Coverage for harbor_cli/utils/utils.py: 94%

41 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-09 12:09 +0100

1"""Utility functions that can't be neatly categorized, or are so niche 

2that they don't need their own module.""" 

3from __future__ import annotations 

4 

5from typing import Any 

6from typing import Iterable 

7from typing import MutableMapping 

8from typing import TypeVar 

9 

10MappingType = TypeVar("MappingType", bound=MutableMapping[str, Any]) 

11 

12 

13def replace_none(d: MappingType, replacement: Any = "") -> MappingType: 

14 """Replaces None values in a dict with a given replacement value. 

15 Iterates recursively through nested dicts and iterables. 

16 

17 Untested with iterables other than list, tuple, and set. 

18 """ 

19 

20 if d is None: 

21 return replacement 

22 

23 def _try_convert_to_original_type( 

24 value: Iterable[Any], original_type: type 

25 ) -> Iterable[Any]: 

26 """Try to convert an iterable to the original type. 

27 If the original type cannot be constructed with an iterable as 

28 the only argument, return a list instead. 

29 """ 

30 try: 

31 return original_type(value) 

32 except TypeError: 

33 return list(value) 

34 

35 def _iter_iterable(value: Iterable[Any]) -> Iterable[Any]: 

36 """Iterates through an iterable recursively, replacing None values.""" 

37 t = type(value) 

38 v_generator = (item if item is not None else replacement for item in value) 

39 values = [] 

40 

41 for item in v_generator: 

42 v = None 

43 if isinstance(item, MutableMapping): 

44 v = replace_none(item) 

45 elif isinstance(item, str): # don't need to recurse into each char 

46 v = item # type: ignore 

47 elif isinstance(item, Iterable): 

48 v = _iter_iterable(item) # type: ignore 

49 else: 

50 v = item 

51 values.append(v) 

52 if values: 52 ↛ 55line 52 didn't jump to line 55, because the condition on line 52 was never false

53 return _try_convert_to_original_type(values, t) 

54 else: 

55 return value 

56 

57 for key, value in d.items(): 

58 if isinstance(value, MutableMapping): 

59 d[key] = replace_none(value) 

60 elif isinstance(value, str): 

61 d[key] = value 

62 elif isinstance(value, Iterable): 

63 d[key] = _iter_iterable(value) 

64 elif value is None: 

65 d[key] = replacement 

66 return d