diff --git a/corsika/detail/output/OutputManager.inl b/corsika/detail/output/OutputManager.inl index 28edfece0c820e6b241ca746cb8f84b641d4cd7b..e7bebabe1e4415132a7942be93fd139ca784de7b 100644 --- a/corsika/detail/output/OutputManager.inl +++ b/corsika/detail/output/OutputManager.inl @@ -144,6 +144,10 @@ namespace corsika { summary["runtime"] = (durationDays ? fmt::format("+{}d ", durationDays) : "") + fmt::format("{:%H:%M:%S}", end_time - start_time); + std::vector<std::string> output_dirs; + for (auto const& outs : outputs_) { output_dirs.push_back(outs.first); } + summary["output_dirs"] = output_dirs; + return summary; } diff --git a/python/corsika/__init__.py b/python/corsika/__init__.py index 078d688098095f06e2a14d2db4c203370993d578..aadda9685eb3863ff1e2be364b837e0a35d0dd82 100644 --- a/python/corsika/__init__.py +++ b/python/corsika/__init__.py @@ -8,9 +8,18 @@ the license. """ +import logging + from . import io from .io.library import Library +logger = logging.getLogger("corsika") +fmt = "[%(levelname)s] - %(name)s - %(message)s" +myFormatter = logging.Formatter(fmt) +handler = logging.StreamHandler() +handler.setFormatter(myFormatter) +logger.addHandler(handler) + # all imported objects __all__ = ["io", "Library"] diff --git a/python/corsika/io/library.py b/python/corsika/io/library.py index 7eabcfe807370fba775ac2c08ccd0a2f53449b49..ad1f520524f2a685ab1c8d37b146fec3fe3fce12 100644 --- a/python/corsika/io/library.py +++ b/python/corsika/io/library.py @@ -11,7 +11,7 @@ import logging import os import os.path as op -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union import yaml @@ -45,12 +45,28 @@ class Library(object): # store the top-level path self.path = path + output_dirs = None + # load the config and summary files self.config = self.load_config(path) self.summary = self.load_summary(path) + if self.summary is None: + msg = f"Missing summary file in '{path}'." + msg += " The simulation may not have finished. Will not load library" + logging.getLogger("corsika").warning(msg) + return + + if "output_dirs" in self.summary.keys(): + output_dirs = self.summary["output_dirs"] + msg = f"Reading in sub-directories: {output_dirs}" + logging.getLogger("corsika").debug(msg) + else: + msg = "Sub-directories not specified in summary.yaml file." + msg += " Will find then dynamically" + logging.getLogger("corsika").debug(msg) # build the list of outputs - self.__outputs = self.__build_outputs(path) + self.__outputs = self.__build_outputs(path, output_dirs) @property def names(self) -> List[str]: @@ -66,14 +82,14 @@ class Library(object): if name in self.__outputs: return self.__outputs[name] else: - msg = f"Output with name '{name}' not available in this library." + msg = f"Output with name '{name}' not available in this library. Skipping." logging.getLogger("corsika").warning(msg) return None @staticmethod - def load_config(path: str) -> Dict[str, Any]: + def __load_yaml(path: str, yaml_name: str) -> Optional[Dict[str, Any]]: """ - Load the top-level config from a given library path. + Load the yaml from a given library path. Parameters @@ -81,22 +97,45 @@ class Library(object): path: str The path to the directory containing the library. + yaml_name: str + The name of the yaml file in `path` + + Returns ------- - dict: - The config as a python dictionary. + dict or None: + A dict of the yaml file, if valid directory, otherwise returns `None` + + """ + config_file = op.join(path, yaml_name) + if op.exists(config_file): + with open(config_file, "r") as f: + return yaml.load(f, Loader=yaml.Loader) + + return None + + @staticmethod + def load_config(path: str) -> Optional[Dict[str, Any]]: + """ + Load the top-level config from a given library path. - Raises - ------ - FileNotFoundError - If the config file cannot be found + + Parameters + ---------- + path: str + The path to the directory containing the library. + + Returns + ------- + dict or None: + The config if valid directory, otherwise returns None """ - with open(op.join(path, "config.yaml"), "r") as f: - return yaml.load(f, Loader=yaml.Loader) + + return Library.__load_yaml(path, "config.yaml") @staticmethod - def load_summary(path: str) -> Dict[str, Any]: + def load_summary(path: str) -> Optional[Dict[str, Any]]: """ Load the top-level summary from a given library path. @@ -108,17 +147,12 @@ class Library(object): Returns ------- - dict: - The summary as a python dictionary. - - Raises - ------ - FileNotFoundError - If the summary file cannot be found + dict or None: + The config if valid directory, otherwise returns None """ - with open(op.join(path, "summary.yaml"), "r") as f: - return yaml.load(f, Loader=yaml.Loader) + + return Library.__load_yaml(path, "summary.yaml") @staticmethod def __valid_library(path: str) -> bool: @@ -143,12 +177,16 @@ class Library(object): # the config file exists, we load it config = Library.load_config(path) + if config is None: + return False # and check that the config's "writer" key is correct return config["creator"] == "CORSIKA8" @staticmethod - def __build_outputs(path: str) -> Dict[str, outputs.Output]: + def __build_outputs( + path: str, dirs: Union[list, None] + ) -> Dict[str, outputs.Output]: """ Build the outputs contained in this library. @@ -161,14 +199,19 @@ class Library(object): path: str The path to the directory containing this library. + dirs: list[str] + List of directory names that will be read in. + If None, will attempt to find the directory names + Returns ------- Dict[str, Output]: A dictionary mapping names to initialized outputs. """ - # get a list of the subdirectories in the library - _, dirs, _ = next(os.walk(path)) + if dirs is None: + # if not supplied, get a list of the subdirectories in the library + _, dirs, _ = next(os.walk(path)) # this is the dictionary where we store our components components: Dict[str, Any] = {} @@ -177,6 +220,11 @@ class Library(object): for subdir in dirs: # read the config file for this output config = Library.load_config(op.join(path, subdir)) + if config is None: + msg = "Could not find a configuration file in" + msg += f" {op.join(path, subdir)}. Skipping sub-directory" + logging.getLogger("corsika").warning(msg) + continue # the name keyword is our unique identifier name = config.get("name") @@ -212,7 +260,7 @@ class Library(object): except AttributeError as e: msg = ( f"Unable to instantiate an instance of '{out_type}' " - f"for a process called '{name}'" + f"for a process called '{name}'. Skipping '{subdir}'" ) logging.getLogger("corsika").warning(msg) logging.getLogger("corsika").warning(e)