Coverage for C: \ Users \ peaco \ OneDrive \ Documents \ GitHub \ mt_metadata \ mt_metadata \ common \ list_dict.py: 90%
151 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-10 00:11 -0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-10 00:11 -0800
1# -*- coding: utf-8 -*-
2"""
3Created on Wed Nov 16 11:08:25 2022
5@author: jpeacock
6"""
7# =============================================================================
8# Imports
9# =============================================================================
10from collections import OrderedDict
13# =============================================================================
16class ListDict:
17 """
18 Hack together an object that acts like a dictionary and list such that a
19 user can get an item by index or key.
21 This is the first attempt, seems to work, might think about inheriting
22 an OrderedDict and overloading.
24 """
26 def __init__(self, values={}):
27 self._home = OrderedDict(values)
29 def __str__(self):
30 lines = ["Contents:", "-" * 12]
31 for k, v in self._home.items():
32 lines.append(f"\t{k} = {v}")
34 return "\n".join(lines)
36 def __repr__(self):
37 """
38 Return a string representation that is consistent across Python versions.
39 """
40 if not self._home:
41 return "ListDict({})"
43 # Create a consistent representation using standard dict format
44 items = []
45 for key, value in self._home.items():
46 # Use repr() for both key and value to handle proper quoting
47 items.append(f"{repr(key)}: {repr(value)}")
49 items_str = "{" + ", ".join(items) + "}"
50 return f"ListDict({items_str})"
52 def __eq__(self, other):
53 return self._home.__eq__(other._home)
55 def __len__(self):
56 return self._home.__len__()
58 def _get_key_from_index(self, index):
59 try:
60 return next(key for ii, key in enumerate(self._home) if ii == index)
62 except StopIteration:
63 raise KeyError(f"Could not find {index}")
65 def _get_index_from_key(self, key):
66 try:
67 return next(index for index, k in enumerate(self._home) if k == key)
69 except StopIteration:
70 raise KeyError(f"Could not find {key}")
72 def _get_key_from_object(self, obj):
73 """
74 Get the key from the metadata object
76 :param obj: DESCRIPTION
77 :type obj: TYPE
78 :return: DESCRIPTION
79 :rtype: TYPE
81 """
82 if hasattr(obj, "id"):
83 return obj.id
84 elif hasattr(obj, "component"):
85 return obj.component
86 else:
87 raise TypeError("could not identify an appropriate key from object")
89 def __deepcopy__(self, memodict={}):
90 """
91 Need to skip copying the logger
92 need to copy properties as well.
94 :return: DESCRIPTION
95 :rtype: TYPE
97 """
98 copied = type(self)()
99 for key, value in self.items():
100 if hasattr(value, "copy"):
101 value = value.copy()
102 copied[key] = value
104 return copied
106 def copy(self):
107 """
108 Copy object
110 """
112 return self.__deepcopy__()
114 def _get_index_slice_from_slice(self, items, key_slice):
115 """
116 Get the slice index values from either an integer or key value
118 :param items: DESCRIPTION
119 :type items: TYPE
120 :param key_slice: DESCRIPTION
121 :type key_slice: TYPE
122 :raises TypeError: DESCRIPTION
123 :return: DESCRIPTION
124 :rtype: TYPE
126 """
127 if key_slice.start is None or isinstance(key_slice.start, int):
128 start = key_slice.start
129 elif isinstance(key_slice.start, str):
130 start = self._get_index_from_key(key_slice.start)
131 else:
132 raise TypeError("Slice start must be type int or str")
134 if key_slice.stop is None or isinstance(key_slice.stop, int):
135 stop = key_slice.stop
136 elif isinstance(key_slice.stop, str):
137 stop = self._get_index_from_key(key_slice.stop)
138 else:
139 raise TypeError("Slice stop must be type int or str")
141 return slice(start, stop, key_slice.step)
143 def __getitem__(self, value):
144 if isinstance(value, str):
145 try:
146 return self._home[value]
147 except KeyError:
148 raise KeyError(f"Could not find {value}")
150 elif isinstance(value, int):
151 key = self._get_key_from_index(value)
152 return self._home[key]
154 elif isinstance(value, slice):
155 return ListDict(
156 list(self.items())[
157 self._get_index_slice_from_slice(self.items(), value)
158 ]
159 )
161 else:
162 raise TypeError("Index must be a string or integer value.")
164 def __setitem__(self, index, value):
165 if isinstance(index, str):
166 self._home[index] = value
168 elif isinstance(index, int):
169 try:
170 key = self._get_key_from_index(index)
171 except KeyError:
172 try:
173 key = self._get_key_from_object(value)
174 except TypeError:
175 key = str(index)
177 self._home[key] = value
179 elif isinstance(index, slice):
180 raise NotImplementedError(
181 "Setting values from slice is not implemented yet"
182 )
184 def __iter__(self):
185 return iter(self.values())
187 def keys(self):
188 return list(self._home.keys())
190 def values(self):
191 return list(self._home.values())
193 def items(self):
194 return self._home.items()
196 def append(self, obj):
197 """
198 Append an object
200 :param obj: DESCRIPTION
201 :type obj: TYPE
202 :return: DESCRIPTION
203 :rtype: TYPE
205 """
207 try:
208 key = self._get_key_from_object(obj)
209 except TypeError:
210 key = str(len(self.keys()))
212 self._home[key] = obj
214 def remove(self, key):
215 """
216 remove an item based on key or index
218 :param key: DESCRIPTION
219 :type key: TYPE
220 :return: DESCRIPTION
221 :rtype: TYPE
223 """
225 if isinstance(key, str):
226 self._home.__delitem__(key)
228 elif isinstance(key, int):
229 key = self._get_key_from_index(key)
230 self._home.__delitem__(key)
231 elif key is None:
232 try:
233 self._home.__delitem__(key)
234 except KeyError:
235 raise (KeyError("Could not find None in keys."))
237 else:
238 raise TypeError("could not identify an appropriate key from object")
240 def extend(self, other, skip_keys=[]):
241 """
242 extend the dictionary from another ListDict object
244 :param other: DESCRIPTION
245 :type other: TYPE
246 :return: DESCRIPTION
247 :rtype: TYPE
249 """
250 if isinstance(skip_keys, str):
251 skip_keys = [skip_keys]
253 if isinstance(other, (ListDict, dict, OrderedDict)):
254 for key, value in other.items():
255 if key in skip_keys:
256 continue
257 self._home[key] = value
259 else:
260 raise TypeError(f"Cannot extend from {type(other)}")
262 def sort(self, inplace=True):
263 """
264 sort the dictionary keys into alphabetical order
265 """
267 od = OrderedDict()
268 for key in sorted(self._home.keys()):
269 od[key] = self._home[key]
271 if inplace:
272 self._home = od
273 else:
274 return od
276 def update(self, other):
277 """
278 Update from another ListDict
279 """
281 if not isinstance(other, (ListDict, dict, OrderedDict)):
282 raise TypeError(
283 f"Cannot update from {type(other)}, must be a "
284 "ListDict, dict, OrderedDict"
285 )
287 self._home.update(other)
289 def pop(self, key):
290 """
291 pop item off of dictionary. The key must be verbatim
293 :param key: key of item to be popped off of dictionary
294 :type key: string
295 :return: item popped
297 """
299 if key in self.keys():
300 return dict([self._home.popitem(key)])
301 else:
302 raise KeyError(f"{key} is not in ListDict keys.")
304 def update_keys(self):
305 """
306 Update all keys in the ListDict based on the current state of the objects.
308 This is useful when object IDs have been changed after being added to the ListDict.
309 The method will re-key all objects using their current ID values.
311 Returns
312 -------
313 dict
314 A dictionary mapping old keys to new keys for objects that were re-keyed.
315 """
316 updates = {}
317 new_home = OrderedDict()
319 for old_key, obj in self._home.items():
320 try:
321 new_key = self._get_key_from_object(obj)
322 except TypeError:
323 # If we can't get a key from the object, keep the old key
324 new_key = old_key
326 if old_key != new_key:
327 updates[old_key] = new_key
329 new_home[new_key] = obj
331 self._home = new_home
332 return updates
334 def to_dict(self, single=False, nested=False, required=False) -> None:
335 """need to implement this method"""
336 return None
338 def clear(self) -> None:
339 """Clear all items from the ListDict."""
340 self._home.clear()