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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.