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
« 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
5import yaml
7from navigaattori import ENCODING, log
10@no_type_check
11class Approvals:
12 """Represent a list of approvals (pairs of role and name) in sequence."""
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)
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
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}):')
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 = ''
66 self.approvals_have_content()
68 if not self.state_code:
69 self.load_approvals()
71 if not self.state_code:
72 log.info(f'sequence of approvals from ({self.approvals_path}) is valid')
74 def is_valid(self) -> bool:
75 """Is the model valid?"""
76 return not self.state_code
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
82 @no_type_check
83 def container(self):
84 """Return the approvals sequence."""
85 return copy.deepcopy(self.approvals_sequence)