[SOLVED] Python dataclass attributes monkeypatching


This Content is from Stack Overflow. Question asked by ALittleMoron

I have problem with my class Config, that is works as proxy between user and ini file. It can load parameters from ini files and set them to its name equivalent in dataclass. I’ve realized, that it I want to get some attribute with dot like Config()._BASE_DIR, it returns str value, because ConfigParser can get values as a str. My idea is to create some method, which will patch all my attributes with property and property.setter to make possible to access dataclass attributes using dot, but wrap them with annotation classes, so, for example, Config()._minAR will return not 4.0 as string but as float.

Is my idea acceptable, or do I need to do it differently?

Config code parts:

import configparser
import pathlib
from dataclasses import dataclass
from itertools import zip_longest

class Config:

    _CONF_PARSER: configparser.ConfigParser = configparser.ConfigParser()

    _BASE_TABLE_FILE_SUFFIX: str = '.csv'

    _BASE_DIR: pathlib.Path = pathlib.Path().absolute()
    _CONF_PATH: pathlib.Path = _BASE_DIR / 'conf'
    _CONF_FILE_PATH: pathlib.Path = _CONF_PATH / 'settings.ini'
    _DATA_TABLE_PATH: pathlib.Path = _CONF_PATH / ('_data_table' + _BASE_TABLE_FILE_SUFFIX)

    _minAR: float = 4.0
    _maxAR: float = 5.0

    CATCH_TIME: int = 6

    def __init__(self) -> None:

    def check_synchronized(self) -> tuple[bool, str]:
        if not self.CONF_PARSER.has_section('settings'):
            return False, 'ini'

        parser_config = self.CONF_PARSER['settings'].items()
        python_config = {
            k: v
            for k, v in self.__dataclass_fields__.items()
             if k not in self._IGNORE_FIELDS

        for pair_1, pair_2 in zip_longest(python_config, parser_config, fillvalue=(None, None)):
            key_1, val_1 = pair_1
            if key_1 is None:
                return False, 'script'

            key_2, val_2 = pair_2
            if key_2 is None:
                return False, 'ini'

            if key_2 in self._IGNORE_FIELDS:
            if key_1.lower() != key_2.lower() or (default := str(val_1.default)) != val_2:
                mode = 'ini' if default != str(getattr(self, key_1)) else 'script'
                return False, mode
        return True, 'both'

    def updateFromIni(self):
        for key, value in self.CONF_PARSER['settings'].items():
            upper_key = key.upper()
            if str(getattr(self, upper_key)) == value:

            setattr(self, upper_key, value)

    def prepare(self):

        is_sync, mode = self.check_synchronized()

        if is_sync:

        if mode == 'ini' or mode == 'both':
        elif mode == 'script':

    def _writeAll(self):
        if not self.CONF_PARSER.has_section('settings'):

        for key, field in self.__dataclass_fields__.items():
            if key in self._IGNORE_FIELDS:
            self.CONF_PARSER.set('settings', key, str(field.default))


    def _writeInFile(self):
        with open(self.CONF_FILE_PATH, 'w') as file:

    def _createConfDir(self) -> None:
        if not self.CONF_PATH.exists():
            self.CONF_PATH.mkdir(parents=True, exist_ok=True)

    def setValue(self, field, value):
        if not hasattr(self, field) or field in self._IGNORE_FIELDS:

        setattr(self, field, value)
        if not isinstance(value, str):
            value = str(value)
        self.CONF_PARSER.set('settings', field, value)

More context: I use dataclass with configParser to make my Config class able to do the following things:

  1. Sync attributes with ini file (if no ini file, create it from Config structure with default values; if Config not syncronized with ini file, load from ini, and write to ini, it ini-file has wrong structure, or some values are incorrect) to avoid the situation, when user accidentally delete ini file;
  2. Set and Get all existing values in config from any part of my program (it is PyQt6 application);
  3. Save it state from one session (application run) to another.

So, I had no idea, what other structure of config class I should have used, except for this. If you have better idea for synchronizable config, tell me.


I’ve discovered, that only one change, that I need to make my Config class make custom dot access to attributes, is to write custom magic method __getattribute__ in my class.


import configparser
import pathlib
from dataclasses import dataclass
from itertools import zip_longest
from typing import Any


class Config:
    # some code ...

    def __getattribute__(self, __name: str) -> Any:
        if __name == 'ACCESS_FIELDS':
            return ACCESS_FIELDS

        attr = super().__getattribute__(__name)

        if __name in ACCESS_FIELDS:
            _type = self.__annotations__[__name]
            return _type(attr)
        return attr

    # other code ...

I created variable with accessed fields not in class body, because in other cases, if I get ACCESS_FIELDS by using Config.ACCESS_FIELDS or self.ACCESS_FIELDS, it will call __getattrubute__ method again and cause recursion error.

Basically, I got all what I need by using this solution, but I still has problem with setValue method. I’ve discovered, that __setattr__ overriden method works not so good with __getattribute__ overriden method in my class (it cause recursion error too). Probably, I’ll restructure my Config class, but not now.

This Question was asked in StackOverflow by ALittleMoron and Answered by ALittleMoron It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?