Coverage for hookee/hookeemanager.py: 82.05%

1 import time

2

3 import click

4 from hookee.exception import HookeeError, HookeeConfigError

5

6 from hookee.conf import Config

7 from hookee.pluginmanager import PluginManager

8 from hookee.server import Server

9 from hookee.tunnel import Tunnel

10 from hookee.util import PrintUtil

11

12 __author__ = "Alex Laird"

13 __copyright__ = "Copyright 2023, Alex Laird"

14 __version__ = "2.0.8"

15

16

17 class 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()