9 from typing
import Dict, Any, List, Tuple
15 from multiprocessing
import Process, Queue
28 from validationplots
import create_plots
29 import validationfunctions
32 g_plottingProcesses: Dict[str, Tuple[Process, Queue, Dict[str, Any]]] = ({})
35 def get_revision_label_from_json_filename(json_filename: str) -> str:
37 Gets the label of a revision from the path to the revision.json file
38 for example results/r121/revision.json
39 will result in the label r121
40 This is useful if the results folder has been moved by the user
42 folder_part = os.path.split(json_filename)[0]
43 last_folder = os.path.basename(folder_part)
48 def get_json_object_list(results_folder: str, json_file_name: str) -> List[str]:
50 Searches one folder's sub-folder for json files of a
51 specific name and returns a combined list of the
55 search_string = results_folder +
"/*/" + json_file_name
57 found_revs = glob(search_string)
60 for r_file
in found_revs:
62 with open(r_file)
as json_file:
63 data = json.load(json_file)
66 found_rev_labels.append(
67 get_revision_label_from_json_filename(r_file)
70 return found_rev_labels
73 def deliver_json(file_name: str):
75 Simply load & parse a json file and return the
79 with open(file_name)
as json_file:
80 data = json.load(json_file)
84 def create_revision_key(revision_names: List[str]) -> str:
86 Create a string key out of a revision list, which is handed to tho browser
87 in form of a progress key
89 return functools.reduce(
lambda x, y: x +
"-" + y, revision_names,
"")
92 def check_plotting_status(progress_key: str):
94 Check the plotting status via the supplied progress_key
97 if progress_key
not in g_plottingProcesses:
100 process, qu, last_status = g_plottingProcesses[progress_key]
105 while not qu.empty():
106 msg = qu.get_nowait()
110 g_plottingProcesses[progress_key] = (process, qu, last_status)
118 def warn_wrong_directory():
119 if not os.getcwd().endswith(
"html"):
121 f
"ERROR: Expected to be in HTML directory, but my current "
122 f
"working directory is {os.getcwd()}; abspath: {os.getcwd()}."
127 def start_plotting_request(
128 revision_names: List[str], results_folder: str
131 Start a new comparison between the supplied revisions
137 rev_key = create_revision_key(revision_names)
140 if rev_key
in g_plottingProcesses:
141 logging.info(f
"Plotting request for {rev_key} still running")
158 os.path.dirname(results_folder),
162 g_plottingProcesses[rev_key] = (p, qu,
None)
164 logging.info(f
"Started process for plotting request {rev_key}")
172 Root Validation class to handle non-static HTTP requests into the
173 validation server. The two main functions are to hand out compiled json
174 objects of revisions and comparisons and to start and monitor the
175 creation of comparison plots.
181 class initializer, which takes the path to the folders containing the
182 validation run results and plots (aka comparison)
193 os.environ[
"BELLE2_LOCAL_DIR"]
197 @cherrypy.tools.json_in()
198 @cherrypy.tools.json_out()
201 Triggers the start of a now comparison between the revisions supplied
204 rev_list = cherrypy.request.json[
"revision_list"]
205 logging.debug(
"Creating plots for revisions: " + str(rev_list))
206 progress_key = start_plotting_request(
209 return {
"progress_key": progress_key}
214 forward to the static landing page if
215 the default url is used (like http://localhost:8080/)
217 raise cherrypy.HTTPRedirect(
"/static/validation.html")
222 Serve file from the html/plot directory.
223 :param args: For the request /plots/a/b/c, these will be the strings
227 warn_wrong_directory()
230 raise cherrypy.HTTPError(404)
232 tag_folder = os.path.relpath(
238 path = os.path.join(tag_folder, *args[-2:])
239 return cherrypy.lib.static.serve_file(path)
242 @cherrypy.tools.json_in()
243 @cherrypy.tools.json_out()
246 Checks on the status of a comparison creation
248 progress_key = cherrypy.request.json[
"input"]
249 logging.debug(
"Checking status for plot creation: " + str(progress_key))
250 status = check_plotting_status(progress_key)
254 @cherrypy.tools.json_out()
257 Return a combined json object with all revisions and
258 mark the newest one with the field most_recent=true
262 rev_list = get_json_object_list(
264 validationpath.file_name_results_json,
269 reference_revision = json.loads(
275 full_path = os.path.join(
278 validationpath.file_name_results_json,
282 lbl_folder = get_revision_label_from_json_filename(full_path)
283 j = deliver_json(full_path)
284 j[
"label"] = lbl_folder
285 combined_list.append(j)
294 def sort_key(label: str):
297 f
"Misformatted label encountered: '{label}' "
298 f
"(doesn't seem to include date?)"
301 category, datetag = label.split(
"-", maxsplit=1)
302 print(category, datetag)
306 order = [
"release",
"prerelease",
"build",
"nightly"]
308 index = order.index(category)
312 f
"Misformatted label encountered: '{label}' (doesn't seem "
313 f
"to belong to any known category?)"
315 return f
"{index}-{datetag}"
317 combined_list.sort(key=
lambda rev: sort_key(rev[
"label"]), reverse=
True)
320 combined_list = [reference_revision] + combined_list
325 for r
in combined_list:
326 rdate_str = r[
"creation_date"]
327 if isinstance(rdate_str, str):
328 if len(rdate_str) > 0:
330 rdate = time.strptime(rdate_str,
"%Y-%m-%d %H:%M")
339 if newest_date
is None:
342 if rdate > newest_date:
346 for c
in combined_list:
347 if c[
"most_recent"]
is not None:
348 c[
"most_recent"] =
False
352 newest_rev[
"most_recent"] =
True
355 return {
"revisions": combined_list}
358 @cherrypy.tools.json_out()
361 return the json file of the comparison results of one specific
365 warn_wrong_directory()
379 if not os.path.isfile(path):
380 raise cherrypy.HTTPError(
381 404, f
"Json Comparison file {path} does not exist"
384 return deliver_json(path)
387 @cherrypy.tools.json_out()
391 JSON file containing git versions and time of last restart
394 warn_wrong_directory()
399 "last_restart": self.
last_restartlast_restart.strftime(
"%-d %b %H:%M ")
401 "version_restart": self.
versionversion,
403 os.environ[
"BELLE2_LOCAL_DIR"]
408 def setup_gzip_compression(path, cherry_config):
410 enable GZip compression for all text-based content the
411 web-server will deliver
414 cherry_config[path].update(
416 "tools.gzip.on":
True,
417 "tools.gzip.mime_types": [
421 "application/javascript",
428 def get_argument_parser():
429 """Prepare a parser for all the known command line arguments"""
432 parser = argparse.ArgumentParser()
438 help=
"The IP address on which the"
439 "server starts. Default is '127.0.0.1'.",
446 help=
"The port number on which"
447 " the server starts. Default is '8000'.",
454 help=
"Open validation website" " in the system's default browser.",
459 help=
"Run in production environment: "
460 "no log/error output via website and no auto-reload",
467 def parse_cmd_line_arguments():
469 Sets up a parser for command line arguments,
470 parses them and returns the arguments.
471 @return: An object containing the parsed command line arguments.
472 Arguments are accessed like they are attributes of the object,
473 i.e. [name_of_object].[desired_argument]
475 parser = get_argument_parser()
477 return parser.parse_args()
483 parse_command_line=False,
491 format=
"%(asctime)s %(levelname)-8s %(message)s",
496 cwd_folder = os.getcwd()
500 os.environ.get(
"BELLE2_RELEASE_DIR",
None)
is None
501 and os.environ.get(
"BELLE2_LOCAL_DIR",
None)
is None
503 sys.exit(
"Error: No basf2 release set up!")
505 cherry_config = dict()
507 cherry_config[
"/"] = {}
509 setup_gzip_compression(
"/", cherry_config)
512 static_folder_list = [
"validation",
"html_static"]
515 if basepath[
"central"]
is not None:
516 static_folder_central = os.path.join(
517 basepath[
"central"], *static_folder_list
519 if os.path.isdir(static_folder_central):
520 static_folder = static_folder_central
524 if basepath[
"local"]
is not None:
525 static_folder_local = os.path.join(
526 basepath[
"local"], *static_folder_list
528 if os.path.isdir(static_folder_local):
529 static_folder = static_folder_local
531 if static_folder
is None:
533 "Either BELLE2_RELEASE_DIR or BELLE2_LOCAL_DIR has to bet "
534 "to provide static HTML content. Did you run b2setup ?"
541 logging.info(f
"Serving static content from {static_folder}")
542 logging.info(f
"Serving result content and plots from {cwd_folder}")
545 if not os.path.isdir(results_folder):
547 "Result folder {} does not exist, run validate_basf2 first "
548 "to create validation output".format(results_folder)
553 os.path.isdir(os.path.join(results_folder, f))
554 for f
in os.listdir(results_folder)
557 if results_count == 0:
559 f
"Result folder {results_folder} contains no folders, run "
560 f
"validate_basf2 first to create validation output"
564 if not os.path.exists(
"html"):
568 if not os.path.exists(
"plots"):
572 cherry_config[
"/static"] = {
573 "tools.staticdir.on":
True,
575 "tools.staticdir.match":
r"^.*\.(js|css|html|png|js.map)$",
576 "tools.staticdir.dir": static_folder,
578 setup_gzip_compression(
"/static", cherry_config)
581 cherry_config[
"/plots"] = {
582 "tools.staticdir.on":
True,
584 "tools.staticdir.match":
r"^.*\.(png|json|pdf)$",
585 "tools.staticdir.dir": comparison_folder,
587 setup_gzip_compression(
"/plots", cherry_config)
590 cherry_config[
"/results"] = {
591 "tools.staticdir.on":
True,
592 "tools.staticdir.dir": results_folder,
594 "tools.staticdir.match":
r"^.*\.(log|root)$",
598 "tools.staticdir.content_types": {
599 "log":
"text/plain; charset=utf-8",
600 "root":
"application/octet-stream",
604 setup_gzip_compression(
"/results", cherry_config)
608 production_env =
False
609 if parse_command_line:
611 cmd_arguments = parse_cmd_line_arguments()
613 ip = cmd_arguments.ip
614 port = int(cmd_arguments.port)
615 open_site = cmd_arguments.view
616 production_env = cmd_arguments.production
618 cherrypy.config.update(
619 {
"server.socket_host": ip,
"server.socket_port": port, }
622 cherrypy.config.update({
"environment":
"production"})
624 logging.info(f
"Server: Starting HTTP server on {ip}:{port}")
627 webbrowser.open(
"http://" + ip +
":" + str(port))
635 if __name__ ==
"__main__":
def __init__(self, working_folder)
def check_comparison_status(self)
def create_comparison(self)
last_restart
Date when this object was instantiated.
def comparisons(self, comparison_label=None)
def revisions(self, revision_label=None)
working_folder
html folder that contains plots etc.
Optional[str] get_compact_git_hash(str repo_folder)
def get_html_plots_tag_comparison_folder(output_base_dir, tags)
def get_html_folder(output_base_dir)
def get_html_plots_folder(output_base_dir)
def get_results_folder(output_base_dir)