33"""
44
55import json
6+ import logging
67import os
78import importlib .metadata
89import string
910from collections import defaultdict
1011from typing import Optional , Type , List , Dict , Any , Callable
1112import inspect
12- from tools .logging_wrapper import Logger
13+ from . tools .logging_wrapper import Logger
1314
1415
1516class ConfigDSL :
@@ -149,10 +150,10 @@ def get_with_default(
149150 if options is not None and current_element not in options :
150151 raise RuntimeError ("Wrong option" )
151152 except BaseException as e :
152- self .handle_fmt_error (fmt_error , e , kwargs )
153+ self .handle_fmt_error (fmt_error or "" , e , kwargs )
153154 return default
154155
155- self .handle_fmt_success (fmt_success , current_element , kwargs )
156+ self .handle_fmt_success (fmt_success or "" , current_element , kwargs )
156157 return current_element
157158
158159 def get_or_raise (
@@ -197,10 +198,10 @@ def get_or_raise(
197198 if options is not None and current_element not in options :
198199 raise RuntimeError ("Wrong option" )
199200 except BaseException as e :
200- self .handle_fmt_error (fmt_error , e , kwargs )
201+ self .handle_fmt_error (fmt_error or "" , e , kwargs )
201202 raise e
202203
203- self .handle_fmt_success (fmt_success , current_element , kwargs )
204+ self .handle_fmt_success (fmt_success or "" , current_element , kwargs )
204205 return current_element
205206
206207 def handle_fmt (
@@ -382,6 +383,55 @@ def config_entry(func: Callable) -> Callable:
382383 return ConfigEntryProxy (func )
383384
384385
386+ class ConfigLogContext :
387+ """
388+ Provides a context in which the loggers of the config can be optionally switched.
389+ If one log uses a high log level, this can disable log-outputs for the subsequent code block.
390+ """
391+
392+ def __init__ (self , config : "Config" , should_swap : bool ):
393+ """
394+ Constructs a ConfigLogContext object.
395+
396+ Parameters
397+ ----------
398+ config : Config
399+ Config object.
400+ should_swap : bool
401+ Should the loggers be swapped.
402+ """
403+ self ._config : Config = config
404+ self ._should_swap : bool = should_swap
405+
406+ def swap (self ):
407+ """
408+ Swaps the loggers used by the config object.
409+ """
410+ tmp = self ._config ._logger
411+ self ._config ._logger = self ._config ._logger_null
412+ self ._config ._logger_null = tmp
413+
414+ def __enter__ (self ) -> "ConfigLogContext" :
415+ """
416+ Called upon entering a with ... statement.
417+
418+ Returns
419+ -------
420+ ctx : ConfigLogContext
421+ Logging context.
422+ """
423+ if self ._should_swap :
424+ self .swap ()
425+ return self
426+
427+ def __exit__ (self , * args ) -> None :
428+ """
429+ Called upon exiting a with ... statement.
430+ """
431+ if self ._should_swap :
432+ self .swap ()
433+
434+
385435class Config :
386436 """
387437 Handles the reading of parameters in the JSON configuration file provided by the user. This class is based on
@@ -401,16 +451,20 @@ def __init__(self, config_file_name: str):
401451 self ._base_dir : str = os .path .dirname (
402452 os .path .join (os .getcwd (), config_file_name )
403453 )
404- self ._logger : Optional [Logger ] = None
454+ self ._logger_null : Logger = Logger (Config .__name__ , level = logging .CRITICAL )
455+ self ._logger : Logger = self ._logger_null
405456 self ._data : Optional [Dict [str , Any ]] = None
406457 self ._fields : Dict [str , Any ] = defaultdict (lambda : None )
407458
408459 # attach external backing field to all config entries
460+ # inspection triggers assertions: need to temporarily set to value other than None
461+ self ._data = {}
409462 config_entries = inspect .getmembers (
410463 self , lambda f : hasattr (f , "attach_instance" )
411464 )
412465 for _ , e in config_entries :
413466 e .attach_instance (self )
467+ self ._data = None
414468
415469 def set_logger (self , logger : Logger ):
416470 """
@@ -423,12 +477,34 @@ def set_logger(self, logger: Logger):
423477 """
424478 self ._logger = logger
425479
480+ def show_log_if (self , cond : bool ) -> ConfigLogContext :
481+ """
482+ Redirects log requests in the resulting context if cond is True.
483+
484+ Usage:
485+ with self.show_log_if(cond):
486+ ... load fields ...
487+
488+ Hereby default arguments can be loaded for inactive modules without emitting log output.
489+
490+ Parameters
491+ ----------
492+ cond : bool
493+ Should redirect?
494+
495+ Returns
496+ -------
497+ ctx : ConfigLogContext
498+ Logging context
499+ """
500+ return ConfigLogContext (self , not cond )
501+
426502 @property
427503 def json (self ) -> ConfigDSL :
428504 """
429505 Returns and Object to load JSON values dynamically.
430506 """
431- assert self ._data is not None and self . _logger is not None
507+ assert self ._data is not None
432508 return ConfigDSL (self ._data , self ._logger )
433509
434510 def _read_json_base (self , config_file_name : str ):
@@ -440,7 +516,6 @@ def _read_json_base(self, config_file_name: str):
440516 config_file_name : string
441517 Path to the JSON configuration file
442518 """
443- assert self ._logger is not None
444519 self ._logger .log_info_rank_zero (
445520 f"Micro Manager version: { importlib .metadata .version ('micro-manager-precice' )} "
446521 )
@@ -517,8 +592,7 @@ def _read_json_base(self, config_file_name: str):
517592 # ======================================================
518593 # Tasking
519594 # ======================================================
520-
521- if self .json ["tasking" ].exists ():
595+ with self .show_log_if (self .json ["tasking" ].exists ()):
522596 self .tasking_backend .set = self .json ["tasking" ]["backend" ].get_with_default (
523597 "socket" ,
524598 "Tasking backend: {data}" ,
@@ -558,7 +632,6 @@ def read_json_micro_manager(self):
558632 Reads Micro Manager relevant information from JSON configuration file
559633 and saves the data to the respective instance attributes.
560634 """
561- assert self ._logger is not None
562635 self ._read_json_base (self ._config_file_name )
563636
564637 self .precice_config_file_name .set = os .path .join (
@@ -636,7 +709,7 @@ def read_json_micro_manager(self):
636709 "Adaptivity settings are provided but adaptivity is turned off."
637710 )
638711
639- if self .enable_adaptivity ():
712+ with self .show_log_if ( self . enable_adaptivity () ):
640713 self .adaptivity_type .set = self .json ["simulation_params" ][
641714 "adaptivity_settings"
642715 ]["type" ].get_with_default (
@@ -749,6 +822,7 @@ def read_json_micro_manager(self):
749822 else :
750823 self .write_data_names ().append ("rank_of_sim" )
751824
825+ with self .show_log_if (self .enable_load_balancing ()):
752826 self .load_balancing_n .set = self .json ["simulation_params" ][
753827 "load_balancing_settings"
754828 ]["every_n_time_windows" ].get_with_default (
@@ -877,7 +951,6 @@ def read_json_snapshot(self):
877951 """
878952 Reads Snapshot relevant information from JSON configuration file
879953 """
880- assert self ._logger is not None
881954 self ._read_json_base (self ._config_file_name ) # Read base information
882955 self ._logger .log_info_rank_zero (
883956 f"Reading JSON configuration file: { self ._config_file_name } "
0 commit comments