Coverage for src/CSET/cset_workflow/app/finish_website/bin/finish_website.py: 100%
40 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-05 21:08 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-05 21:08 +0000
1#!/usr/bin/env python3
2# © Crown copyright, Met Office (2022-2025) and CSET contributors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
16"""
17Write finished status to website front page.
19Constructs the plot index, and does the final update to the workflow status on
20the front page of the web interface.
21"""
23import datetime
24import json
25import logging
26import os
27import shutil
28from importlib.metadata import version
29from pathlib import Path
31from CSET._common import combine_dicts, sort_dict
33logging.basicConfig(
34 level=os.getenv("LOGLEVEL", "INFO"), format="%(asctime)s %(levelname)s %(message)s"
35)
38def construct_index():
39 """Construct the plot index.
41 Index should has the form ``{"Category Name": {"recipe_id": "Plot Name"}}``
42 where ``recipe_id`` is the name of the plot's directory.
43 """
44 index = {}
45 plots_dir = Path(os.environ["CYLC_WORKFLOW_SHARE_DIR"]) / "web/plots"
46 # Loop over all diagnostics and append to index.
47 for metadata_file in plots_dir.glob("**/*/meta.json"):
48 try:
49 with open(metadata_file, "rt", encoding="UTF-8") as fp:
50 plot_metadata = json.load(fp)
52 category = plot_metadata["category"]
53 case_date = plot_metadata.get("case_date", "")
54 relative_url = str(metadata_file.parent.relative_to(plots_dir))
56 record = {
57 category: {
58 case_date if case_date else "Aggregation": {
59 relative_url: plot_metadata["title"].strip()
60 }
61 }
62 }
63 except (json.JSONDecodeError, KeyError, TypeError) as err:
64 logging.error("%s is invalid, skipping.\n%s", metadata_file, err)
65 continue
66 index = combine_dicts(index, record)
68 # Sort index of diagnostics.
69 index = sort_dict(index)
71 # Write out website index.
72 with open(plots_dir / "index.json", "wt", encoding="UTF-8") as fp:
73 json.dump(index, fp, indent=2)
76def update_workflow_status():
77 """Update the workflow status on the front page of the web interface."""
78 web_dir = Path(os.environ["CYLC_WORKFLOW_SHARE_DIR"] + "/web")
79 with open(web_dir / "status.html", "wt") as fp:
80 finish_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
81 fp.write(f"<p>Completed at {finish_time} using CSET v{version('CSET')}</p>\n")
84def copy_rose_config():
85 """Copy the rose-suite.conf file to add to output web directory."""
86 rose_suite_conf = Path(os.environ["CYLC_WORKFLOW_RUN_DIR"]) / "rose-suite.conf"
87 web_conf_file = Path(os.environ["CYLC_WORKFLOW_SHARE_DIR"]) / "web/rose-suite.conf"
88 shutil.copy(rose_suite_conf, web_conf_file)
91def run():
92 """Do the final steps to finish the website."""
93 construct_index()
94 update_workflow_status()
95 copy_rose_config()
98if __name__ == "__main__": # pragma: no cover
99 run()