Coverage for /home/ubuntu/hidebound/python/hidebound/server/app.py: 42%
108 statements
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-05 23:50 +0000
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-05 23:50 +0000
1from typing import Any, Dict, List, Tuple, Union # noqa F401
2from dash import dash_table # noqa F401
4import os
6from dash import dcc
7from dash.dependencies import Input, Output, State
8from dash.exceptions import PreventUpdate
9import dash
10import flask
11from flask import current_app
13import hidebound.core.tools as hbt
14import hidebound.server.components as components
15import hidebound.server.extensions as ext
16import hidebound.server.server_tools as hst
19TESTING = hbt.str_to_bool(os.environ.get('HIDEBOUND_TESTING', 'True')) # pragma: no cover
21# setup directories in /tmp/mnt
22if hbt.str_to_bool(os.environ.get('_HIDEBOUND_TEST_DIRS', 'False')):
23 hst.setup_hidebound_directories('/tmp/mnt') # pragma: no cover
24# ------------------------------------------------------------------------------
27'''
28Hidebound service used for displaying and interacting with Hidebound database.
29'''
32EP = hst.EndPoints()
35# TODO: implement this
36def liveness():
37 # type: () -> None
38 '''Liveness probe for kubernetes.'''
39 pass
42# TODO: implement this
43def readiness():
44 # type: () -> None
45 '''
46 Readiness probe for kubernetes.
47 '''
48 pass
51def get_app(testing=False):
52 # type: (bool) -> dash.Dash
53 '''
54 Creates a Hidebound app.
56 Returns:
57 Dash: Dash app.
58 '''
59 app = flask.Flask('hidebound') # type: Union[flask.Flask, dash.Dash]
60 app.config['TESTING'] = testing
61 app.config['HEALTHZ'] = dict(
62 live=liveness,
63 ready=readiness,
64 )
66 ext.swagger.init_app(app)
67 ext.hidebound.init_app(app)
68 ext.healthz.init_app(app)
70 app = components.get_dash_app(app, seconds=0.8)
71 return app
74APP = get_app(testing=TESTING)
75SERVER = APP.server
78@APP.server.route('/static/<stylesheet>')
79def serve_stylesheet(stylesheet):
80 # type: (str) -> flask.Response
81 '''
82 Serve stylesheet to app.
84 Args:
85 stylesheet (str): stylesheet filename.
87 Returns:
88 flask.Response: Response.
89 '''
90 params = dict(
91 COLOR_SCHEME=components.COLOR_SCHEME,
92 FONT_FAMILY=components.FONT_FAMILY,
93 )
94 content = hst.render_template(stylesheet + '.j2', params)
95 return flask.Response(content, mimetype='text/css')
98# EVENTS------------------------------------------------------------------------
99# TODO: Find a way to test events.
100@APP.callback(
101 output=Output('store', 'data'),
102 inputs=[
103 Input('workflow-button', 'n_clicks'),
104 Input('update-button', 'n_clicks'),
105 Input('create-button', 'n_clicks'),
106 Input('export-button', 'n_clicks'),
107 Input('delete-button', 'n_clicks'),
108 Input('search-button', 'n_clicks'),
109 Input('dropdown', 'value'),
110 Input('query', 'value'),
111 Input('query', 'n_submit'),
112 ],
113 state=[State('store', 'data')],
114 prevent_initial_call=True,
115)
116def on_event(*inputs):
117 # type: (Tuple[Any, ...]) -> Dict[str, Any]
118 '''
119 Update Hidebound database instance, and updates store with input data.
121 Args:
122 inputs (tuple): Input elements.
124 Returns:
125 dict: Store data.
126 '''
127 APP.logger.debug(f'on_event called with inputs: {str(inputs)[:50]}')
128 hb = current_app.extensions['hidebound']
130 # get context values
131 context = dash.callback_context
132 store = context.states['store.data'] or {} # type: dict
133 trigger = context.triggered_id
134 query = context.inputs['query.value']
135 group_by_asset = context.inputs['dropdown.value'] == 'asset'
137 initial_query = context.inputs['query.n_submit'] == 0
138 ready = getattr(APP, 'ready', False)
139 if initial_query and not ready:
140 APP.ready = True
141 raise PreventUpdate
143 if trigger == 'workflow-button':
144 hst.request(store, EP.workflow, dict(steps=hb.config['workflow']))
145 store = hst.search(store, query, group_by_asset)
147 elif trigger == 'update-button':
148 hst.request(store, EP.update)
149 store = hst.search(store, query, group_by_asset)
151 elif trigger == 'create-button':
152 hst.request(store, EP.create)
154 elif trigger == 'export-button':
155 hst.request(store, EP.export)
157 elif trigger == 'delete-button':
158 hst.request(store, EP.delete)
160 elif trigger in ['search-button', 'query']:
161 if store.get('ready', False):
162 store = hst.search(store, query, group_by_asset)
164 return store
167@APP.callback(
168 Output('table-content', 'children'),
169 [Input('store', 'data')]
170)
171def on_datatable_update(store):
172 # type: (Dict) -> dash_table.DataTable
173 '''
174 Updates datatable with read information from store.
176 Args:
177 store (dict): Store data.
179 Returns:
180 DataTable: Dash DataTable.
181 '''
182 APP.logger.debug(
183 f'on_datatable_update called with store: {str(store)[:50]}'
184 )
186 if store in [{}, None]:
187 raise PreventUpdate
188 data = store.get('content', None)
189 if data is None:
190 raise PreventUpdate
192 if 'error' in data.keys():
193 return components.get_key_value_card(data, header='error', id_='error')
194 return components.get_datatable(data['response'])
197@APP.callback(
198 Output('content', 'children'),
199 [Input('tabs', 'value')],
200 [State('store', 'data')],
201)
202def on_get_tab(tab, store):
203 # type: (str, Dict) -> Union[flask.Response, List, None]
204 '''
205 Serve content for app tabs.
207 Args:
208 tab (str): Name of tab to render.
209 store (dict): Store.
211 Returns:
212 flask.Response: Response.
213 '''
214 hb = current_app.extensions['hidebound']
216 APP.logger.debug(
217 f'on_get_tab called with tab: {tab} and store: {str(store)[:50]}'
218 )
219 store = store or {}
221 if tab == 'data':
222 query = store.get('query', None)
223 return components.get_data_tab(query)
225 elif tab == 'graph':
226 data = store.get('content', None)
227 if data is None:
228 return None
230 if 'error' in data.keys():
231 return components.get_key_value_card(
232 data, header='error', id_='error'
233 )
234 graph = data['response']
235 if len(graph) == 0:
236 return None
237 return components.get_asset_graph(graph)
239 elif tab == 'config':
240 config = hst.format_config(
241 hb.config,
242 redact_regex=hb.config['redact_regex'],
243 redact_hash=hb.config['redact_hash'],
244 )
245 return components.get_config_tab(config)
247 elif tab == 'api':
248 return dcc.Location(id='api', pathname='/api')
250 elif tab == 'docs':
251 return dcc.Location(
252 id='docs',
253 href='https://thenewflesh.github.io/hidebound'
254 )
257@APP.callback(
258 Output('progressbar-container', 'children'),
259 [Input('clock', 'n_intervals')],
260)
261def on_progress(timestamp):
262 # type: (int) -> flask.Response
263 '''
264 Updates progressbar.
266 Args:
267 timestamp (int): Store modification timestamp.
269 Returns:
270 flask.Response: Response.
271 '''
272 return components.get_progressbar(hst.get_progress())
273# ------------------------------------------------------------------------------
276if __name__ == '__main__':
277 debug = os.environ.get('HIDEBOUND_TESTING', False)
278 APP.run_server(debug=debug, host=EP.host, port=EP.port)