Source code for hookee.hookeemanager
__copyright__ = "Copyright (c) 2020-2024 Alex Laird"
__license__ = "MIT"
import os
import time
import click
from hookee import __version__
from hookee.conf import Config
from hookee.exception import HookeeConfigError, HookeeError
from hookee.pluginmanager import PluginManager
from hookee.server import Server
from hookee.tunnel import Tunnel
from hookee.util import PrintUtil
banner_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "banner.txt")
with open(banner_path) as f:
banner = f.read()
[docs]
class HookeeManager:
"""
An object that manages the state of a ``hookee`` runtime. Reads app configuration, loads enabled plugins,
and manages the long-lived state of ``hookee`` if a server and tunnel are started.
If instantiating for a custom integration, pass a :class:`~hookee.conf.Config` with args that otherwise would have
been passed to the CLI (see ``hookee --help``). For example:
.. code-block:: python
from hookee import HookeeManager
from hookee.conf import Config
config = Config(subdomain="my_domain",
region="eu")
hookee_manager = HookeeManager(config=config)
A ``response_callback`` function can also be passed instead of defining a raw ``response`` and ``content-type``
(or needing to use plugins) when integrating with ``hookee``:
.. code-block:: python
from hookee import HookeeManager
from hookee.conf import Config
def response_callback(request, response):
response.data = "<Response>Ok</Response>"
response.headers["Content-Type"] = "application/xml"
return response
config = Config(response_callback=response_callback)
hookee_manager = HookeeManager(config=config)
:var ctx: The ``click`` context.
:vartype ctx: click.Context
:var config: The ``hookee`` configuration.
:vartype config: Config
:var plugin_manager: Reference to the Plugin Manager.
:vartype plugin_manager: PluginManager
:var print_util: Reference to the PrintUtil.
:vartype print_util: PrintUtil
:var tunnel: Reference to the Tunnel.
:vartype tunnel: Tunnel
:var server: Reference to the Server.
:vartype server: Server
:var alive: ``True`` when this object is managing an active tunnel and server.
:vartype alive: bool
"""
def __init__(self, config=None, load_plugins=True):
self.ctx = click.get_current_context(silent=True)
if config is None:
try:
data = self.ctx.obj if self.ctx is not None else {}
config = Config(**data)
except HookeeConfigError as e:
self.fail(str(e), e)
self.config = config
self.plugin_manager = PluginManager(self)
self.print_util = PrintUtil(self.config)
if load_plugins:
self.plugin_manager.load_plugins()
self.tunnel = Tunnel(self)
self.server = Server(self)
self.alive = False
self.print_hookee_banner()
[docs]
def run(self):
"""
If one is not already running, start a managed server and tunnel and block until an interrupt
is received (or ``alive`` is set to ``False``).
"""
if not self.alive:
try:
self._init_server_and_tunnel()
while self.alive:
time.sleep(1)
except KeyboardInterrupt:
pass
self.stop()
[docs]
def stop(self):
"""
If running, shutdown the managed server and tunnel.
"""
if self.alive:
self.server.stop()
if self.tunnel._thread:
self.tunnel._thread.alive = False
# Wait for the other threads to teardown
while self.server._thread and self.tunnel._thread:
time.sleep(1)
self.alive = False
def print_hookee_banner(self):
self.print_util.print_open_header("", "=")
self.print_util.print_basic(banner.format(version=__version__), color="green", bold=True)
self.print_util.print_basic()
self.print_util.print_close_header("=", blank_line=False)
def print_ready(self):
self.print_util.print_open_header("Registered Plugins")
plugins = self.plugin_manager.enabled_plugins()
self.print_util.print_basic(f" * Enabled Plugins: {plugins}")
if self.plugin_manager.response_callback:
self.print_util.print_basic(" Response callback: enabled")
self.print_util.print_close_header()
self.print_util.print_open_header("Registered Endpoints")
rules = list(filter(lambda r: r.rule not in ["/shutdown", "/static/<path:filename>", "/status"],
self.server.app.url_map.iter_rules()))
for rule in rules:
self.print_util.print_basic(f" * {self.tunnel.public_url}{rule.rule}", print_when_logging=True)
self.print_util.print_basic(f" Methods: {sorted(list(rule.methods))}", print_when_logging=True)
self.print_util.print_close_header()
self.print_util.print_basic()
self.print_util.print_basic("--> Ready, send a request to a registered endpoint ...", color="green", bold=True)
self.print_util.print_basic()
[docs]
def fail(self, msg, e=None):
"""
Shutdown the current application with a failure. If a CLI Context exists, that will be used to invoke the
failure, otherwise an exception will be thrown for failures to be caught.
:param msg: The failure message.
:type msg: str
:param e: The error being raised.
:type e: HookeeError, optional
"""
if self.ctx is not None:
self.ctx.fail(msg)
elif e:
raise e
else:
raise HookeeError(msg)
def _init_server_and_tunnel(self):
self.alive = True
self.server.start()
self.tunnel.start()
self.print_ready()