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>
asked Oct 30, 2021 at 17:36
\$\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"]
Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
answered Oct 31, 2021 at 7:58
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.