Coverage for hookee/server.py : 95.00%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import logging
2import threading
3import time
4from http import HTTPStatus
5from urllib.error import URLError
6from urllib.request import urlopen, Request
8from flask import Flask
10from hookee import pluginmanager
12__author__ = "Alex Laird"
13__copyright__ = "Copyright 2020, Alex Laird"
14__version__ = "2.0.0"
16werkzeug_logger = logging.getLogger("werkzeug")
17werkzeug_logger.setLevel(logging.ERROR)
20class Server:
21 """
22 An object that manages a non-blocking Flask server and thread.
24 :var hookee_manager: Reference to the ``hookee`` Manager.
25 :vartype hookee_manager: HookeeManager
26 :var plugin_manager: Reference to the Plugin Manager.
27 :vartype plugin_manager: PluginManager
28 :var print_util: Reference to the PrintUtil.
29 :vartype print_util: PrintUtil
30 :var port: The server's port.
31 :vartype port: int
32 :var app: The Flask app.
33 :vartype app: flask.Flask
34 """
36 def __init__(self, hookee_manager):
37 self.hookee_manager = hookee_manager
38 self.plugin_manager = self.hookee_manager.plugin_manager
39 self.print_util = self.hookee_manager.print_util
40 self.port = self.hookee_manager.config.get("port")
42 self.app = self.create_app()
44 self._thread = None
46 def create_app(self):
47 """
48 Create a Flask app and register all Blueprints found in enabled plugins.
50 :return: The Flask app.
51 :rtype: flask.Flask
52 """
53 app = Flask(__name__)
55 app.config.from_mapping(
56 ENV="development"
57 )
59 for plugin in self.plugin_manager.get_plugins_by_type(pluginmanager.BLUEPRINT_PLUGIN):
60 app.register_blueprint(plugin.blueprint)
62 return app
64 def _loop(self):
65 thread = None
67 try:
68 thread = threading.current_thread()
69 thread.alive = True
71 # This will block until stop() is invoked to shutdown the Werkzeug server
72 self.app.run(host="127.0.0.1", port=self.port, debug=True, use_reloader=False)
73 except OSError as e:
74 self.print_util.print_basic(e)
76 self.stop()
78 if thread:
79 thread.alive = False
81 def start(self):
82 """
83 If one is not already running, start a server in a new thread.
84 """
85 if self._thread is None:
86 self.print_util.print_open_header("Starting Server")
88 self._thread = threading.Thread(target=self._loop)
89 self._thread.start()
91 while self._server_status() != HTTPStatus.OK:
92 time.sleep(1)
94 self.print_close_header()
96 def stop(self):
97 """
98 If running, kill the server and cleanup its thread.
99 """
100 if self._thread:
101 req = Request("http://127.0.0.1:{}/shutdown".format(self.port), method="POST")
102 urlopen(req)
104 self._thread = None
106 def _server_status(self):
107 """
108 Get the response code of the server's ``/status`` endpoint.
110 :return: The status code.
111 :rtype: http.HTTPStatus
112 """
113 try:
114 return urlopen("http://127.0.0.1:{}/status".format(self.port)).getcode()
115 except URLError:
116 return HTTPStatus.INTERNAL_SERVER_ERROR
118 def print_close_header(self):
119 self.print_util.print_basic(" * Port: {}".format(self.port))
120 self.print_util.print_basic(" * Blueprints: registered")
121 self.print_util.print_close_header()