Search code examples
pythonconfigurationyamlconfigpyyaml

yaml.constructor.ConstructorError: while constructing a Python object cannot find "module_name" in the module '__main__'


I have tried construct some classes from file.yml

Stucture of priters.yml you can see bellow:

--- !Station
recipients:
        - &first_phone ['Max']
        - &second_phone ['Anna', 'Alisa']
obj:
        - &first !!python/object:__main__.Nokia
                model: Nokia_8800
                recipients: *first_phone
        - &second !!python/object:__main__.Apple
                model: iPhone4
                recipients: *second_phone
spam_station: !station
    Nokia: *first
    Apple: *second

The class constructor is presented in spam_station.py

from abc import ABC, abstractmethod
from typing import Union
from yaml import YAMLObject, load

class AbstrackPhone(ABC):
    @abstractmethod
    def send_message(self, message):
        pass

class Nokia(AbstrackPhone):
    def __init__(self):
        self.model = None
        self.recipients = []

    def send_message(self, message):
        for recipient in self.recipients:
            print(f"Hi {recipient} i'm {self.model}. {message}")


class Apple(AbstrackPhone):
    def __init__(self):
        self.model = None
        self.recipients = []

    def send_message(self, message):
        for recipient in self.recipients:
            print(f"Hi {recipient} i'm {self.model}. {message}")


class ConstructStation(YAMLObject):
    yaml_tag = u'!Station'

    @classmethod
    def from_yaml(Class, loader, node):
        def get_satation(loader, node):
            data = loader.construct_mapping(node)
            station = Class.Station()
            station.add_phones(data.values())
            return station

        loader.add_constructor(u"!station", get_satation)
        return loader.construct_mapping(node)['spam_station']

    class Station():

        def __init__(self):
            self.senders = []

        def add_phones(self, phones: Union[list, str]):
            self.senders.extend(phones)

        def send_message(self, message, **kwargs):
            for sender in self.senders:
                sender.send_message(message, **kwargs)

def station():
    with open('../yaml_config/printers') as file:
        spam_station = load(file)
    return spam_station

if __name__ == "__main__":
    station().send_message('Good luck!!!')

I've tried import and use 'station' in sender.py:

from station.spam_station import station

if __name__ == "__main__":
    station().send_message('Good luck!!!')

when i run spam_station.py, it's ok:

Hi Max i'm Nokia_8800. Good luck!!!
Hi Anna i'm iPhone4. Good luck!!!
Hi Alisa i'm iPhone4. Good luck!!!

when i run sender.py, i've error:

yaml.constructor.ConstructorError: while constructing a Python object
cannot find 'Nokia' in the module '__main__'
  in "../yaml_config/printers", line 7, column 11

How to solve this problem? Please tell me, what is a good practice for configuring python objects to yaml. Thanks!


Solution

  • The problem is that when you execute sender.py, that file (and not spam_station.py) is __main__.

    Probably the best solution is to avoid depending on import paths in the YAML file. You already do that with Station, so you can simply do it on the other classes as well:

    class Nokia(AbstrackPhone, YAMLObject):
        yaml_tag = u"!Nokia"
        def __init__(self, model = None, recipients = []):
            self.model = model
            self.recipients = recipients
    
        def send_message(self, message):
            for recipient in self.recipients:
                print(f"Hi {recipient} i'm {self.model}. {message}")
    

    Now you can use !Nokia instead of !!python/object:__main__.Nokia in the YAML file. Same goes for the Apple class.