Goal
My program takes a yaml config file as parameter python myprogram.py cfg.yml
.
- all modules can access the cfg content with a simple module import
- the imported
config
behaves like the loaded top-level yaml dictionary for reading operations
# module_a.py from config_module import cfg_class as config print(config['pi'])
Reasoning
I wanted to follow the official python FAQ to share config global variables across modules but I could only define the parameters in a pre-defined file that cannot be changed by the user:
# config_module.py pi = 3.14
To let the user choose a yaml config file, I changed config.py to:
# config_module.py class cfg_class: pass
and the very top of myprogram.py
to
# myprogram.py from config_module import cfg_class as config import yaml if __name__=='__main__': with open(sys.argv[1], 'r') as f: cfg_dict = yaml.safe_load(f) config.cfg_dict = cfg_dict
As a result all modules have access to the config content
# module_a.py from config_module import cfg_class as config print(config.cfg_dict['pi'])
Instead of using config.cfg_dict['pi']
I wanted to use config['pi']
. To do that I defined the __getitem__
for the cfg_class
:
class cfg_class: def __getitem__(self, x): return self.cfg_dict[x]
It failed with TypeError: 'type' object is not subscriptable
. An explanation to this problem is given here. It indicates that we need a metaclass for cfg_class:
# config.py class Meta(type): def __getitem__(self, x): return cfg_class.cfg_dict[x] class cfg_class(metaclass=Meta): pass
And now it works. Below is the code for
config_module.py myprogram.py module_a.py module_b.py cfg.yml
Any feedback?
Working code
# config_module.py class Meta(type): def __getitem__(self, x): return cfg_class.cfg_dict[x] class cfg_class(metaclass=Meta): pass
# myprogram.py import sys import yaml from config_module import cfg_class as config import module_a import module_b if __name__=='__main__': with open(sys.argv[1], 'r') as f: cfg_dict = yaml.safe_load(f) config.cfg_dict = cfg_dict module_a.a_class_from_module_a() module_b.a_class_from_module_b()
# module_a.py from config_module import cfg_class as config class a_class_from_module_a: def __init__(self): print( 'I am an instance of a_class_from_module_a' ' and I have access to config: ', config['pi'] )
# module_b.py from config_module import cfg_class as config class a_class_from_module_b: def __init__(self): print( 'I am an instance of a_class_from_module_b' ' and I have access to config: ', config['pi'] )
# cfg.yml --- pi: 3.14 ...
Result:
$ python myprogram.py cfg.yml >> I am an instance of a_class_from_module_a and I have access to config: 3.14 >> I am an instance of a_class_from_module_b and I have access to config: 3.14
Edit: simpler solution thanks to @MiguelAlorda who almost got it all right and @RootTwo who fixed the problem
# config_module.py import yaml config = {} def load_config(file_str: str) -> None: global config with open(file_str) as f: # config = yaml.safe_load(f) # does not work because it unbinds config, see comment from @RootTwo config.update(yaml.safe_load(f))
# myprogram.py import sys import config_module import module_a import module_b if __name__=='__main__': config_module.load_config(sys.argv[1]) x = module_a.a_class_from_module_a() y = module_b.a_class_from_module_b() print(x,y)
# module_a.py from config_module import config class a_class_from_module_a: def __init__(self): print( 'I am an instance of a_class_from_module_a' ' and I have access to config: ', config['pi'] )
# module_b.py from config_module import config class a_class_from_module_b: def __init__(self): print( 'I am an instance of a_class_from_module_b' ' and I have access to config: ', config['pi'] )
#cfg.yml --- pi: 3.14 ...
Output:
$ python myprogram.py cfg.yml I am an instance of a_class_from_module_a and I have access to config: 3.14 I am an instance of a_class_from_module_b and I have access to config: 3.14 <module_a.a_class_from_module_a object at 0x000002391D5F8F48> <module_b.a_class_from_module_b object at 0x000002391D5FB288>