2 from typing
import Dict, Any, List, Tuple
8 from multiprocessing
import Process, Queue
21 from validationplots
import create_plots
22 import validationfunctions
25 g_plottingProcesses = {}
28 def get_revision_label_from_json_filename(json_filename: str) -> str:
30 Gets the label of a revision from the path to the revision.json file
31 for example results/r121/revision.json
32 will result in the label r121
33 This is useful if the results folder has been moved by the user
35 folder_part = os.path.split(json_filename)[0]
36 last_folder = os.path.basename(folder_part)
41 def get_json_object_list(results_folder: str, json_file_name: str) -> List[str]:
43 Searches one folder's sub-folder for json files of a
44 specific name and returns a combined list of the
48 search_string = results_folder +
"/*/" + json_file_name
50 found_revs = glob(search_string)
53 for r_file
in found_revs:
55 with open(r_file)
as json_file:
56 data = json.load(json_file)
59 found_rev_labels.append(
60 get_revision_label_from_json_filename(r_file)
63 return found_rev_labels
66 def deliver_json(file_name: str):
68 Simply load & parse a json file and return the
72 with open(file_name)
as json_file:
73 data = json.load(json_file)
77 def create_revision_key(revision_names: List[str]) -> str:
79 Create a string key out of a revision list, which is handed to tho browser
80 in form of a progress key
82 return functools.reduce(
lambda x, y: x +
"-" + y, revision_names,
"")
85 def check_plotting_status(progress_key: str):
87 Check the plotting status via the supplied progress_key
90 if progress_key
not in g_plottingProcesses:
93 process, qu, last_status = \
94 g_plottingProcesses[progress_key]
100 msg = qu.get_nowait()
104 g_plottingProcesses[progress_key] = (process, qu, last_status)
112 def warn_wrong_directory():
113 if not os.getcwd().endswith(
"html"):
114 print(f
"ERROR: Expected to be in HTML directory, but my current "
115 f
"working directory is {os.getcwd()}; abspath: {os.getcwd()}.")
119 def start_plotting_request(revision_names: List[str], results_folder: str) -> str:
121 Start a new comparison between the supplied revisions
127 rev_key = create_revision_key(revision_names)
130 if rev_key
in g_plottingProcesses:
131 logging.info(
"Plotting request for {} still running".format(rev_key))
148 os.path.dirname(results_folder)
152 g_plottingProcesses[rev_key] = (p, qu,
None)
154 logging.info(
"Started process for plotting request {}".format(rev_key))
162 Root Validation class to handle non-static HTTP requests into the
163 validation server. The two main functions are to hand out compiled json
164 objects of revisions and comparisons and to start and monitor the
165 creation of comparison plots.
171 class initializer, which takes the path to the folders containing the
172 validation run results and plots (aka comparison)
183 os.environ[
"BELLE2_LOCAL_DIR"]
187 @cherrypy.tools.json_in()
188 @cherrypy.tools.json_out()
191 Triggers the start of a now comparison between the revisions supplied
194 rev_list = cherrypy.request.json[
"revision_list"]
195 logging.debug(
'Creating plots for revisions: ' + str(rev_list))
196 progress_key = start_plotting_request(
200 return {
"progress_key": progress_key}
205 forward to the static landing page if
206 the default url is used (like http://localhost:8080/)
208 raise cherrypy.HTTPRedirect(
"/static/validation.html")
213 Serve file from the html/plot directory.
214 :param args: For the request /plots/a/b/c, these will be the strings
218 warn_wrong_directory()
221 raise cherrypy.HTTPError(404)
223 tag_folder = os.path.relpath(
229 path = os.path.join(tag_folder, *args[-2:])
230 return cherrypy.lib.static.serve_file(path)
233 @cherrypy.tools.json_in()
234 @cherrypy.tools.json_out()
237 Checks on the status of a comparison creation
239 progress_key = cherrypy.request.json[
"input"]
240 logging.debug(
'Checking status for plot creation: ' +
242 status = check_plotting_status(progress_key)
246 @cherrypy.tools.json_out()
249 Return a combined json object with all revisions and
250 mark the newest one with the field most_recent=true
254 rev_list = get_json_object_list(
256 validationpath.file_name_results_json
261 reference_revision = json.loads(
269 full_path = os.path.join(
272 validationpath.file_name_results_json
276 lbl_folder = get_revision_label_from_json_filename(full_path)
277 j = deliver_json(full_path)
278 j[
"label"] = lbl_folder
279 combined_list.append(j)
288 def sort_key(label: str):
291 f
"Misformatted label encountered: '{label}' "
292 f
"(doesn't seem to include date?)"
295 category, datetag = label.split(
"-", maxsplit=1)
296 print(category, datetag)
307 index = order.index(category)
311 f
"Misformatted label encountered: '{label}' (doesn't seem "
312 f
"to belong to any known category?)"
314 return "{}-{}".format(index, datetag)
317 key=
lambda rev: sort_key(rev[
"label"]),
322 combined_list = [reference_revision] + combined_list
327 for r
in combined_list:
328 rdate_str = r[
"creation_date"]
329 if isinstance(rdate_str, str):
330 if len(rdate_str) > 0:
332 rdate = time.strptime(rdate_str,
"%Y-%m-%d %H:%M")
341 if newest_date
is None:
344 if rdate > newest_date:
348 for c
in combined_list:
349 if c[
"most_recent"]
is not None:
350 c[
"most_recent"] =
False
354 newest_rev[
"most_recent"] =
True
357 return {
"revisions": combined_list}
360 @cherrypy.tools.json_out()
363 return the json file of the comparison results of one specific
367 warn_wrong_directory()
381 if not os.path.isfile(path):
382 raise cherrypy.HTTPError(
384 f
"Json Comparison file {path} does not exist"
387 return deliver_json(path)
390 @cherrypy.tools.json_out()
394 JSON file containing git versions and time of last restart
397 warn_wrong_directory()
403 self.
last_restart.strftime(
"%-d %b %H:%M ") + time.tzname[1],
404 "version_restart": self.
version,
407 os.environ[
"BELLE2_LOCAL_DIR"]
412 def setup_gzip_compression(path, cherry_config):
414 enable GZip compression for all text-based content the
415 web-server will deliver
418 cherry_config[path].update(
420 'tools.gzip.on':
True,
421 'tools.gzip.mime_types': [
425 'application/javascript',
432 def get_argument_parser():
433 """Prepare a parser for all the known command line arguments"""
436 parser = argparse.ArgumentParser()
439 parser.add_argument(
"-ip",
"--ip", help=
"The IP address on which the"
440 "server starts. Default is '127.0.0.1'.",
441 type=str, default=
'127.0.0.1')
442 parser.add_argument(
"-p",
"--port", help=
"The port number on which"
443 " the server starts. Default is '8000'.",
444 type=str, default=8000)
445 parser.add_argument(
"-v",
"--view", help=
"Open validation website"
446 " in the system's default browser.",
448 parser.add_argument(
"--production", help=
"Run in production environment: "
449 "no log/error output via website and no auto-reload",
455 def parse_cmd_line_arguments():
457 Sets up a parser for command line arguments,
458 parses them and returns the arguments.
459 @return: An object containing the parsed command line arguments.
460 Arguments are accessed like they are attributes of the object,
461 i.e. [name_of_object].[desired_argument]
463 parser = get_argument_parser()
465 return parser.parse_args()
468 def run_server(ip='127.0.0.1', port=8000, parse_command_line=False,
469 open_site=False, dry_run=False):
472 logging.basicConfig(level=logging.DEBUG,
473 format=
'%(asctime)s %(levelname)-8s %(message)s',
477 cwd_folder = os.getcwd()
480 if os.environ.get(
'BELLE2_RELEASE_DIR',
None)
is None and os.environ.get(
'BELLE2_LOCAL_DIR',
None)
is None:
481 sys.exit(
'Error: No basf2 release set up!')
483 cherry_config = dict()
485 cherry_config[
"/"] = {}
487 setup_gzip_compression(
"/", cherry_config)
490 static_folder_list = [
"validation",
"html_static"]
493 if basepath[
"central"]
is not None:
494 static_folder_central = os.path.join(
495 basepath[
"central"], *static_folder_list)
496 if os.path.isdir(static_folder_central):
497 static_folder = static_folder_central
501 if basepath[
"local"]
is not None:
502 static_folder_local = os.path.join(
503 basepath[
"local"], *static_folder_list)
504 if os.path.isdir(static_folder_local):
505 static_folder = static_folder_local
507 if static_folder
is None:
508 sys.exit(
"Either BELLE2_RELEASE_DIR or BELLE2_LOCAL_DIR has to bet "
509 "to provide static HTML content. Did you run b2setup ?")
515 logging.info(f
"Serving static content from {static_folder}")
516 logging.info(f
"Serving result content and plots from {cwd_folder}")
519 if not os.path.isdir(results_folder):
520 sys.exit(
"Result folder {} does not exist, run validate_basf2 first "
521 "to create validation output".format(results_folder))
523 results_count = sum([
524 os.path.isdir(os.path.join(results_folder, f))
525 for f
in os.listdir(results_folder)
527 if results_count == 0:
529 f
"Result folder {results_folder} contains no folders, run "
530 f
"validate_basf2 first to create validation output")
533 if not os.path.exists(
'html'):
537 if not os.path.exists(
'plots'):
541 cherry_config[
"/static"] = {
542 'tools.staticdir.on':
True,
544 'tools.staticdir.match':
"^.*\.(js|css|html|png|js.map)$",
545 'tools.staticdir.dir': static_folder
547 setup_gzip_compression(
"/static", cherry_config)
550 cherry_config[
"/plots"] = {
551 'tools.staticdir.on':
True,
553 'tools.staticdir.match':
"^.*\.(png|json|pdf)$",
554 'tools.staticdir.dir': comparison_folder
556 setup_gzip_compression(
"/plots", cherry_config)
559 cherry_config[
"/results"] = {
560 'tools.staticdir.on':
True,
561 'tools.staticdir.dir': results_folder,
563 'tools.staticdir.match':
"^.*\.(log|root)$",
567 'tools.staticdir.content_types': {
568 'log':
'text/plain; charset=utf-8',
569 'root':
'application/octet-stream'
573 setup_gzip_compression(
"/results", cherry_config)
577 production_env =
False
578 if parse_command_line:
580 cmd_arguments = parse_cmd_line_arguments()
582 ip = cmd_arguments.ip
583 port = int(cmd_arguments.port)
584 open_site = cmd_arguments.view
585 production_env = cmd_arguments.production
587 cherrypy.config.update({
'server.socket_host': ip,
588 'server.socket_port': port,
591 cherrypy.config.update({
'environment':
'production'})
593 logging.info(f
"Server: Starting HTTP server on {ip}:{port}")
596 webbrowser.open(
"http://" + ip +
":" + str(port))
601 working_folder=cwd_folder
608 if __name__ ==
'__main__':