Coverage for hookee/hookeemanager.py: 82.05%

78 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-12-06 15:08 +0000

1import time 

2 

3import click 

4from hookee.exception import HookeeError, HookeeConfigError 

5 

6from hookee.conf import Config 

7from hookee.pluginmanager import PluginManager 

8from hookee.server import Server 

9from hookee.tunnel import Tunnel 

10from hookee.util import PrintUtil 

11 

12__author__ = "Alex Laird" 

13__copyright__ = "Copyright 2022, Alex Laird" 

14__version__ = "2.0.7" 

15 

16 

17class HookeeManager: 

18 """ 

19 An object that manages the state of a ``hookee`` runtime. Reads app configuration, loads enabled plugins, 

20 and manages the long-lived state of ``hookee`` if a server and tunnel are started. 

21 

22 If instantiating for a custom integration, pass a :class:`~hookee.conf.Config` with args that otherwise would have 

23 been passed to the CLI (see ``hookee --help``). For example: 

24 

25 .. code-block:: python 

26 

27 from hookee import HookeeManager 

28 from hookee.conf import Config 

29 

30 config = Config(subdomain="my_domain", 

31 region="eu") 

32 hookee_manager = HookeeManager(config=config) 

33 

34 A ``response_callback`` function can also be passed instead of defining a raw ``response`` and ``content-type`` 

35 (or needing to use plugins) when integrating with ``hookee``: 

36 

37 .. code-block:: python 

38 

39 from hookee import HookeeManager 

40 from hookee.conf import Config 

41 

42 def response_callback(request, response): 

43 response.data = "<Response>Ok</Response>" 

44 response.headers["Content-Type"] = "application/xml" 

45 return response 

46 

47 config = Config(response_callback=response_callback) 

48 hookee_manager = HookeeManager(config=config) 

49 

50 :var ctx: The ``click`` context. 

51 :vartype ctx: click.Context 

52 :var config: The ``hookee`` configuration. 

53 :vartype config: Config 

54 :var plugin_manager: Reference to the Plugin Manager. 

55 :vartype plugin_manager: PluginManager 

56 :var print_util: Reference to the PrintUtil. 

57 :vartype print_util: PrintUtil 

58 :var tunnel: Reference to the Tunnel. 

59 :vartype tunnel: Tunnel 

60 :var server: Reference to the Server. 

61 :vartype server: Server 

62 :var alive: ``True`` when this object is managing an active tunnel and server. 

63 :vartype alive: bool 

64 """ 

65 

66 def __init__(self, config=None, load_plugins=True): 

67 self.ctx = click.get_current_context(silent=True) 

68 

69 if config is None: 

70 try: 

71 data = self.ctx.obj if self.ctx is not None else {} 

72 config = Config(**data) 

73 except HookeeConfigError as e: 

74 self.fail(str(e), e) 

75 

76 self.config = config 

77 self.plugin_manager = PluginManager(self) 

78 self.print_util = PrintUtil(self.config) 

79 

80 if load_plugins: 

81 self.plugin_manager.load_plugins() 

82 

83 self.tunnel = Tunnel(self) 

84 self.server = Server(self) 

85 

86 self.alive = False 

87 

88 self.print_hookee_banner() 

89 

90 def run(self): 

91 """ 

92 If one is not already running, start a managed server and tunnel and block until an interrupt 

93 is received (or ``alive`` is set to ``False``). 

94 """ 

95 if not self.alive: 

96 try: 

97 self._init_server_and_tunnel() 

98 

99 while self.alive: 

100 time.sleep(1) 

101 except KeyboardInterrupt: 

102 pass 

103 

104 self.stop() 

105 

106 def stop(self): 

107 """ 

108 If running, shutdown the managed server and tunnel. 

109 """ 

110 if self.alive: 

111 self.server.stop() 

112 if self.tunnel._thread: 

113 self.tunnel._thread.alive = False 

114 

115 # Wait for the other threads to teardown 

116 while self.server._thread and self.tunnel._thread: 

117 time.sleep(1) 

118 

119 self.alive = False 

120 

121 def print_hookee_banner(self): 

122 self.print_util.print_open_header("", "=") 

123 self.print_util.print_basic(""" .__ __  

124 | |__ ____ ____ | | __ ____ ____  

125 | | \ / _ \ / _ \| |/ // __ \_/ __ \  

126 | Y ( <_> | <_> ) <\ ___/\ ___/  

127 |___| /\____/ \____/|__|_ \\___ >\___ > 

128 \/ \/ \/ \/  

129 v{}""".format(__version__), color="green", bold=True) 

130 self.print_util.print_basic() 

131 self.print_util.print_close_header("=", blank_line=False) 

132 

133 def print_ready(self): 

134 self.print_util.print_open_header("Registered Plugins") 

135 

136 plugins = self.plugin_manager.enabled_plugins() 

137 self.print_util.print_basic(" * Enabled Plugins: {}".format(plugins)) 

138 if self.plugin_manager.response_callback: 

139 self.print_util.print_basic(" Response callback: enabled") 

140 

141 self.print_util.print_close_header() 

142 

143 self.print_util.print_open_header("Registered Endpoints") 

144 

145 rules = list(filter(lambda r: r.rule not in ["/shutdown", "/static/<path:filename>", "/status"], 

146 self.server.app.url_map.iter_rules())) 

147 for rule in rules: 

148 self.print_util.print_basic(" * {}{}".format(self.tunnel.public_url, rule.rule), print_when_logging=True) 

149 self.print_util.print_basic(" Methods: {}".format(sorted(list(rule.methods))), print_when_logging=True) 

150 

151 self.print_util.print_close_header() 

152 

153 self.print_util.print_basic() 

154 self.print_util.print_basic("--> Ready, send a request to a registered endpoint ...", color="green", bold=True) 

155 self.print_util.print_basic() 

156 

157 def fail(self, msg, e=None): 

158 """ 

159 Shutdown the current application with a failure. If a CLI Context exists, that will be used to invoke the 

160 failure, otherwise an exception will be thrown for failures to be caught. 

161 

162 :param msg: The failure message. 

163 :type msg: str 

164 :param e: The error being raised. 

165 :type e: HookeeError, optional 

166 """ 

167 if self.ctx is not None: 

168 self.ctx.fail(msg) 

169 elif e: 

170 raise e 

171 else: 

172 raise HookeeError(msg) 

173 

174 def _init_server_and_tunnel(self): 

175 self.alive = True 

176 self.server.start() 

177 self.tunnel.start() 

178 

179 self.print_ready()