Skip to content

Commit

Permalink
- If hookee was instantiated programmatically rather than from the …
Browse files Browse the repository at this point in the history
…CLI (i.e. `click.Context` is `None`), `HookeeManager` throws exceptions and `PrintUtil` appends to a logger instead of interacting with `click.Context` or `echo` functions.
  • Loading branch information
alexdlaird committed Sep 17, 2020
1 parent 38a82a0 commit b06de92
Show file tree
Hide file tree
Showing 12 changed files with 68 additions and 52 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Changed
- `PluginManager`'s `response_body` and `response_content_type` variables have been replaced with `response_callback`, a lambda that is generated if these configuration values are given.
- Removed `PluginManager`'s `request_script` and `response_script` variables, instead these are added to `loaded_plugins` after their `Plugin` is validated and instantiated.
- Removed `PluginManager`'s `request_script` and `response_script` variables, instead these are added to `loaded_plugins` after their `Plugin` is validated and instantiated.
- If `hookee` was instantiated programmatically rather than from the CLI (i.e. `click.Context` is `None`), `HookeeManager` throws exceptions and `PrintUtil` appends to a logger instead of interacting with `click.Context` or `echo` functions.

### Removed
- `conf.Context` in favor of using `click`'s own `Context` object.
- Access to the `click` `Context` except in `HookeeManager`, which now has its own abstraction around `Context`-related actions.
- `conf.Context` in favor of using `click.Context`.
- Access to the `click.Context` except in `HookeeManager`, which now has its own abstraction around such actions.

## [1.1.0](https://github.com/alexdlaird/hookee/compare/1.0.1...1.1.0) - 2019-09-15
### Added
Expand Down
5 changes: 5 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ Under the hood, ``hookee`` uses `Flask <https://flask.palletsprojects.com/en/1.1
`pyngrok <https://pyngrok.readthedocs.io/en/latest/>`_ to open and manage its tunnel. Being familiar with these
two packages would allow ``hookee`` to be configured and extended further.

``hookee`` can also be instantiated programmatically instead of from the console. To integrate with ``hookee``
this way, have a look at the :class:`~hookee.hookeemanager.HookeeManager` as a starting point. When instantiating
``hookee`` this way, be sure to configure logging, otherwise request data that would otherwise be output to the
console (for instance request data dumped from plugins) won't be seen.

For more advanced ``hookee`` usage, its own API documentation is also available.

.. toctree::
Expand Down
30 changes: 13 additions & 17 deletions hookee/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os

import click
import confuse

__author__ = "Alex Laird"
Expand Down Expand Up @@ -58,11 +59,16 @@ def response_callback(request, response):
:vartype config_obj: confuse.core.Configuration
:var config_dir: The directory of the config being used.
:vartype config_dir: str
:var config_filename: The full path to the config file being used.
:vartype config_filename: str
:var config_path: The full path to the config file being used.
:vartype config_path: str
:var config_data: The parsed and validated config data. Use :func:`get`, :func:`set`, and other accessors
to interact with the data.
:vartype config_data: confuse.templates.AttrDict
:var click_ctx: ``True`` if the app is running from a ``click`` CLI, ``False`` if it was started programmatically.
Not persisted to the config file.
:vartype click_ctx: bool
:var response_callback: The response callback function, if defined. Not persisted to the config file.
:vartype response_callback: types.FunctionType, optional
"""

def __init__(self, **kwargs):
Expand All @@ -78,6 +84,8 @@ def __init__(self, **kwargs):

self.config_data = config.get(template)

self.click_ctx = click.get_current_context(silent=True) is not None

if self.config_data.get("response") and self.response_callback:
raise HookeeConfigError("Can't define both \"response\" and \"response_callback\".")
elif self.response_callback and not callable(self.response_callback):
Expand All @@ -93,16 +101,13 @@ def __init__(self, **kwargs):

def get(self, key):
"""
Get the config value for the given key.
Get the config value for the given key of persisted data.
:param key: The key.
:type key: str
:return: The config value.
:rtype: object
"""
if key == "response_callback":
return self.response_callback

return self.config_data[key]

def set(self, key, value):
Expand All @@ -114,17 +119,8 @@ def set(self, key, value):
:param value: The value to set.
:type key: object
"""
if key == "response_callback":
print(value)
print(type(value))
if not callable(value):
raise HookeeConfigError("\"response_callback\" must be a function.")
else:
self.response_callback = value

else:
if value != self.config_data[key]:
self._update_config_objects(key, value)
if value != self.config_data[key]:
self._update_config_objects(key, value)

def append(self, key, value):
"""
Expand Down
16 changes: 9 additions & 7 deletions hookee/hookeemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,7 @@ def __init__(self, config=None, load_plugins=True):
data = self.ctx.obj if self.ctx is not None else {}
config = Config(**data)
except HookeeConfigError as e:
if self.ctx is not None:
self.fail(str(e))
else:
raise e
self.fail(str(e), e)

self.config = config
self.plugin_manager = PluginManager(self)
Expand Down Expand Up @@ -124,6 +121,7 @@ def print_hookee_banner(self):
|___| /\____/ \____/|__|_ \\___ >\___ >
\/ \/ \/ \/
v{}""".format(__version__), fg="green", bold=True)
self.print_util.print_basic()
self.print_util.print_close_header("=", blank_line=False)

def print_ready(self):
Expand All @@ -141,25 +139,29 @@ def print_ready(self):
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(" * {}{}".format(self.tunnel.public_url, rule.rule))
self.print_util.print_basic(" Methods: {}".format(sorted(list(rule.methods))))
self.print_util.print_basic(" * {}{}".format(self.tunnel.public_url, rule.rule), print_when_logging=True)
self.print_util.print_basic(" Methods: {}".format(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 ...", fg="green", bold=True)
self.print_util.print_basic()

def fail(self, msg):
def fail(self, msg, e=None):
"""
Shutdown the curent 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 programmatically.
: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)

Expand Down
7 changes: 2 additions & 5 deletions hookee/pluginmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def load_plugins(self):
if response_content_type and not response_body:
self.hookee_manager.fail("If `--content-type` is given, `--response` must also be given.")

self.response_callback = self.config.get("response_callback")
self.response_callback = self.config.response_callback

if self.response_callback and response_body:
self.hookee_manager.fail("If `response_callback` is given, `response` cannot also be given.")
Expand Down Expand Up @@ -310,10 +310,7 @@ def get_plugin(self, plugin_name):
except ImportError:
self.hookee_manager.fail("Plugin \"{}\" could not be found.".format(plugin_name))
except HookeePluginValidationError as e:
if self.hookee_manager.ctx is not None:
self.hookee_manager.fail(str(e))
else:
raise e
self.hookee_manager.fail(str(e), e)

def enabled_plugins(self):
"""
Expand Down
3 changes: 2 additions & 1 deletion hookee/tunnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,6 @@ def stop(self):

def print_close_header(self):
self.print_util.print_basic(
" * Tunnel: {} -> http://127.0.0.1:{}".format(self.public_url.replace("http://", "https://"), self.port))
" * Tunnel: {} -> http://127.0.0.1:{}".format(self.public_url.replace("http://", "https://"), self.port),
print_when_logging=True)
self.print_util.print_close_header()
38 changes: 24 additions & 14 deletions hookee/util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import inspect
import json
import logging
import os
import sys
import xml.dom.minidom
Expand All @@ -8,7 +9,9 @@

__author__ = "Alex Laird"
__copyright__ = "Copyright 2020, Alex Laird"
__version__ = "1.1.0"
__version__ = "1.2.0"

logger = logging.getLogger(__name__)


class PrintUtil:
Expand All @@ -27,7 +30,7 @@ def console_width(self):
return self.config.get("console_width")

def print_config_update(self, msg):
click.secho("\n--> {}\n".format(msg), fg="green")
self.print_basic("\n--> {}\n".format(msg), fg="green")

def print_open_header(self, title, delimiter="-", fg="green"):
"""
Expand All @@ -42,9 +45,8 @@ def print_open_header(self, title, delimiter="-", fg="green"):
"""
width = int((self.console_width - len(title)) / 2)

click.echo("")
click.secho("{}{}{}".format(delimiter * width, title, delimiter * width), fg=fg, bold=True)
click.echo("")
self.print_basic()
self.print_basic("{}{}{}".format(delimiter * width, title, delimiter * width), fg=fg, bold=True)

def print_close_header(self, delimiter="-", fg="green", blank_line=True):
"""
Expand All @@ -58,8 +60,8 @@ def print_close_header(self, delimiter="-", fg="green", blank_line=True):
:type blank_line: bool
"""
if blank_line:
click.echo("")
click.secho(delimiter * self.console_width, fg=fg, bold=True)
self.print_basic()
self.print_basic(delimiter * self.console_width, fg=fg, bold=True)

def print_dict(self, title, data, fg="green"):
"""
Expand All @@ -72,7 +74,7 @@ def print_dict(self, title, data, fg="green"):
:param fg: The color to make the text.
:type fg: str, optional
"""
click.secho("{}: {}".format(title, json.dumps(data, indent=4)), fg=fg)
self.print_basic("{}: {}".format(title, json.dumps(data, indent=4)), fg=fg)

def print_xml(self, title, data, fg="green"):
"""
Expand All @@ -85,20 +87,28 @@ def print_xml(self, title, data, fg="green"):
:param fg: The color to make the text.
:type fg: str, optional
"""
click.secho("{}: {}".format(title, xml.dom.minidom.parseString(data).toprettyxml()), fg=fg)
self.print_basic("{}: {}".format(title, xml.dom.minidom.parseString(data).toprettyxml()), fg=fg)

def print_basic(self, data="", fg="white", bold=False):
def print_basic(self, msg="", fg="white", bold=False, print_when_logging=False):
"""
Print a status update in a boot sequence.
:param data: The update to print.
:type data: str, optional
:param msg: The update to print.
:type msg: str, optional
:param fg: The color to make the text.
:type fg: str, optional
:param bold: True if the output should be bold.
:param bold: ``True`` if the output should be bold.
:type bold: bool, optional
:param print_when_logging: ``True`` if, when ``click_ctx`` is ``False``, ``msg`` should print to the console
instead appended to the logger.
:type print_when_logging: bool, optional
"""
click.secho(data, fg=fg, bold=bold)
if self.config.click_ctx:
click.secho(msg, fg=fg, bold=bold)
elif not print_when_logging:
logger.info(msg)
else:
print(msg)


def python3_gte():
Expand Down
1 change: 1 addition & 0 deletions tests/managedtestcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def setUp(self):
@classmethod
def setUpClass(cls):
cls.hookee_manager = HookeeManager()
cls.hookee_manager.config.click_ctx = True

cls.hookee_manager._init_server_and_tunnel()

Expand Down
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

__author__ = "Alex Laird"
__copyright__ = "Copyright 2020, Alex Laird"
__version__ = "1.1.0"
__version__ = "1.2.0"


class TestCli(HookeeTestCase):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_hookee_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

__author__ = "Alex Laird"
__copyright__ = "Copyright 2020, Alex Laird"
__version__ = "1.1.0"
__version__ = "1.2.0"


class TestHookeeManager(ManagedTestCase):
Expand Down Expand Up @@ -68,4 +68,4 @@ def test_http_post_json_data(self):
"json_data_1": "json_data_value_1"
}""", out.getvalue())
self.assertEqual(response.headers.get("Content-Type"), "application/json")
self.assertEqual(response.json(), data)
self.assertEqual(response.json(), data)
3 changes: 3 additions & 0 deletions tests/test_hookee_manager_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@


class TestHookeeManagerEdges(HookeeTestCase):
def test_not_click_ctx(self):
self.assertFalse(self.config.click_ctx)

def test_hookee_manager(self):
# GIVEN
hookee_manager = HookeeManager()
Expand Down
4 changes: 2 additions & 2 deletions tests/test_plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def response_callback(request, response):
return response

self.assertEqual(0, len(self.plugin_manager.loaded_plugins))
self.hookee_manager.config.set("response_callback", response_callback)
self.hookee_manager.config.response_callback = response_callback

# WHEN
self.plugin_manager.load_plugins()
Expand Down Expand Up @@ -128,7 +128,7 @@ def test_load_plugins(self):
self.assertTrue(hasattr(plugin.module, "plugin_type"))
self.assertIn(plugin.module.plugin_type, VALID_PLUGIN_TYPES)
self.assertTrue(hasattr(plugin.module, "setup"))
if not plugin.plugin_type != BLUEPRINT_PLUGIN:
if plugin.plugin_type != BLUEPRINT_PLUGIN:
self.assertTrue(hasattr(plugin.module, "run"))

if plugin.name == "custom_request_plugin":
Expand Down

0 comments on commit b06de92

Please sign in to comment.