Coverage for hookee/hookeemanager.py: 82.05%
78 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-06 15:28 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-06 15:28 +0000
1import time
3import click
4from hookee.exception import HookeeError, HookeeConfigError
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
12__author__ = "Alex Laird"
13__copyright__ = "Copyright 2024, Alex Laird"
14__version__ = "2.2.2"
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.
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:
25 .. code-block:: python
27 from hookee import HookeeManager
28 from hookee.conf import Config
30 config = Config(subdomain="my_domain",
31 region="eu")
32 hookee_manager = HookeeManager(config=config)
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``:
37 .. code-block:: python
39 from hookee import HookeeManager
40 from hookee.conf import Config
42 def response_callback(request, response):
43 response.data = "<Response>Ok</Response>"
44 response.headers["Content-Type"] = "application/xml"
45 return response
47 config = Config(response_callback=response_callback)
48 hookee_manager = HookeeManager(config=config)
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 """
66 def __init__(self, config=None, load_plugins=True):
67 self.ctx = click.get_current_context(silent=True)
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)
76 self.config = config
77 self.plugin_manager = PluginManager(self)
78 self.print_util = PrintUtil(self.config)
80 if load_plugins:
81 self.plugin_manager.load_plugins()
83 self.tunnel = Tunnel(self)
84 self.server = Server(self)
86 self.alive = False
88 self.print_hookee_banner()
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()
99 while self.alive:
100 time.sleep(1)
101 except KeyboardInterrupt:
102 pass
104 self.stop()
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
115 # Wait for the other threads to teardown
116 while self.server._thread and self.tunnel._thread:
117 time.sleep(1)
119 self.alive = False
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)
133 def print_ready(self):
134 self.print_util.print_open_header("Registered Plugins")
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")
141 self.print_util.print_close_header()
143 self.print_util.print_open_header("Registered Endpoints")
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)
151 self.print_util.print_close_header()
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()
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.
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)
174 def _init_server_and_tunnel(self):
175 self.alive = True
176 self.server.start()
177 self.tunnel.start()
179 self.print_ready()