1
\$\begingroup\$

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> 
\$\endgroup\$
0

    1 Answer 1

    2
    \$\begingroup\$

    welcome to Code Review!

    I think this approach is a bit overkill. You could simply have a config dict in the config_module, and have a config_module.load_config(file_str: str) -> None function:

    # config_module.py import yaml config = {} def load_config(file_str: str) -> None: global config with open(file_str) as f: config.update(yaml.safe_load(f)) 

    And then in you main:

    import sys import config_module if __name__ == "__main__": config_module.load_config(sys.argv[1]) 

    Using the config:

    from config_module import config config["some_config_key"] 
    \$\endgroup\$
    0

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.