3 from abc import ABC, abstractmethod
4 from decorators import invocation_logged
7 from typing import Dict, Optional, Set
10 logger = logging.getLogger(__file__)
14 """Base class for something that can render."""
25 class abstaining_renderer(renderer):
26 """A renderer that doesn't do it all the time."""
28 def __init__(self, name_to_timeout_dict: Dict[str, int]) -> None:
29 self.name_to_timeout_dict = name_to_timeout_dict
31 for key in name_to_timeout_dict:
32 self.last_runs[key] = 0.0
34 def should_render(self, keys_to_skip: Set[str]) -> Optional[str]:
36 for key in self.name_to_timeout_dict:
38 (now - self.last_runs[key]) > self.name_to_timeout_dict[key]
39 ) and key not in keys_to_skip:
44 def render(self) -> None:
45 tries_per_key: Dict[str, int] = {}
46 keys_to_skip: Set[str] = set()
48 key = self.should_render(keys_to_skip)
51 f'renderer: Found nothing to do in "{self.get_name()}"; returning.'
55 if key in tries_per_key:
56 tries_per_key[key] += 1
58 tries_per_key[key] = 0
59 op = f'{self.get_name()}.{key}'
61 if tries_per_key[key] >= 3:
63 f'renderer: Too many failures in "{op}"; giving up.'
67 msg = f'renderer: executing "{op}"'
68 if tries_per_key[key] > 1:
69 msg = msg + f' (retry #{tries_per_key[key]})'
71 if self.periodic_render(key):
72 logger.debug(f'renderer: {op} succeeded.')
73 self.last_runs[key] = time.time()
75 logger.warning(f'renderer: {op} failed; returned False.')
79 def periodic_render(self, key) -> bool:
82 def get_name(self) -> str:
83 return self.__class__.__name__