Coverage for hookee/server.py: 95.00%

# Coverage for hookee/server.py : 95.00%

## 60 statements

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

1 import logging

2 import threading

3 import time

4 from http import HTTPStatus

5 from urllib.error import URLError

6 from urllib.request import urlopen, Request

7

8 from flask import Flask

9

10 from hookee import pluginmanager

11

12 __author__ = "Alex Laird"

13 __copyright__ = "Copyright 2020, Alex Laird"

14 __version__ = "2.0.0"

15

16 werkzeug_logger = logging.getLogger("werkzeug")

17 werkzeug_logger.setLevel(logging.ERROR)

18

19

20 class Server:

21 """

22 An object that manages a non-blocking Flask server and thread.

23

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 """

35

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")

41

42 self.app = self.create_app()

43

44 self._thread = None

45

46 def create_app(self):

47 """

48 Create a Flask app and register all Blueprints found in enabled plugins.

49

50 :return: The Flask app.

51 :rtype: flask.Flask

52 """

53 app = Flask(__name__)

54

55 app.config.from_mapping(

56 ENV="development"

57 )

58

59 for plugin in self.plugin_manager.get_plugins_by_type(pluginmanager.BLUEPRINT_PLUGIN):

60 app.register_blueprint(plugin.blueprint)

61

62 return app

63

64 def _loop(self):

65 thread = None

66

67 try:

68 thread = threading.current_thread()

69 thread.alive = True

70

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)

75

76 self.stop()

77

78 if thread:

79 thread.alive = False

80

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")

87

88 self._thread = threading.Thread(target=self._loop)

89 self._thread.start()

90

91 while self._server_status() != HTTPStatus.OK:

92 time.sleep(1)

93

94 self.print_close_header()

95

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)

103

104 self._thread = None

105

106 def _server_status(self):

107 """

108 Get the response code of the server's ``/status`` endpoint.

109

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

117

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