Coverage for navigaattori/approvals.py: 100.00%

62 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-04 20:46:10 +00:00

1import copy 

2import pathlib 

3from typing import Union, no_type_check 

4 

5import yaml 

6 

7from navigaattori import ENCODING, log 

8 

9 

10@no_type_check 

11class Approvals: 

12 """Represent a list of approvals (pairs of role and name) in sequence.""" 

13 

14 def approvals_have_content(self) -> None: 

15 """Ensure we received a folder to bootstrap.""" 

16 if not self.approvals_path.is_file() or not self.approvals_path.stat().st_size: 

17 self.state_code = 1 

18 self.state_message = f'approvals ({self.approvals_path}) is no file or empty' 

19 log.error(self.state_message) 

20 

21 def load_approvals(self) -> None: 

22 """Load the sequence of approval pairs (role, name).""" 

23 with open(self.approvals_path, 'rt', encoding=ENCODING) as handle: 

24 data = yaml.safe_load(handle) 

25 if not data: 

26 self.state_code = 1 

27 self.state_message = 'empty approvals?' 

28 log.error(f'approval sequence failed to load any entry from ({self.approvals_path})') 

29 return 

30 peeled = data.get('approvals', []) if isinstance(data, dict) else [] 

31 if not peeled or 'approvals' not in data: 

32 self.state_code = 1 

33 self.state_message = 'no approvals or wrong file?' 

34 log.error(f'approval sequence failed to load anything from ({self.approvals_path})') 

35 return 

36 for entry in peeled: 

37 if not isinstance(entry, dict) or 'role' not in entry or 'name' not in entry: 

38 self.state_code = 1 

39 self.state_message = ( 

40 f'no map or one of the keys (role, name) missing in {entry}' 

41 f' of approvals read from ({self.approvals_path})?' 

42 ) 

43 log.error(f'approval sequence failed to load entry ({entry}) from ({self.approvals_path})') 

44 return 

45 

46 self.approvals_sequence = [{'role': entry['role'], 'name': entry['name']} for entry in peeled] 

47 self.approvals_count = len(self.approvals_sequence) 

48 approval_sin_plu = 'approval' if self.approvals_count == 1 else 'approvals' 

49 log.info(f'approvals sequence loaded {self.approvals_count} {approval_sin_plu} from ({self.approvals_path}):') 

50 for entry in self.approvals_sequence: 

51 log.info(f'- {entry}') 

52 log.info(f'approvals sequence successfully loaded from ({self.approvals_path}):') 

53 

54 def __init__(self, approvals_path: Union[str, pathlib.Path], options: dict[str, bool]): 

55 self._options = options 

56 self.quiet: bool = self._options.get('quiet', False) 

57 self.strict: bool = self._options.get('strict', False) 

58 self.verbose: bool = self._options.get('verbose', False) 

59 self.guess: bool = self._options.get('guess', False) 

60 self.approvals_path: pathlib.Path = pathlib.Path(approvals_path) 

61 self.approvals_sequence = [] 

62 self.approvals_count = len(self.approvals_sequence) 

63 self.state_code = 0 

64 self.state_message = '' 

65 

66 self.approvals_have_content() 

67 

68 if not self.state_code: 

69 self.load_approvals() 

70 

71 if not self.state_code: 

72 log.info(f'sequence of approvals from ({self.approvals_path}) is valid') 

73 

74 def is_valid(self) -> bool: 

75 """Is the model valid?""" 

76 return not self.state_code 

77 

78 def code_details(self) -> tuple[int, str]: 

79 """Return an ordered pair of state code and message""" 

80 return self.state_code, self.state_message 

81 

82 @no_type_check 

83 def container(self): 

84 """Return the approvals sequence.""" 

85 return copy.deepcopy(self.approvals_sequence)