I have a settings file, which is loaded in once and takes the values from the settings json file and uses them as the attribute values. I was thinking of using the @property instead of overriding __getattribute__ but I feel as if there is a better, more elegant solution. Note that the application it is used in is multi-threaded.


class SettingsMeta(type, JsonFileWrapperBase):
    _file_path = os.path.join(os.getcwd(), "..", "data", "settings.json")

    # Declarations to avoid PyCharm highlighting
    manga_save_dir, database_path, total_scrapper_threads, log_file = None, None, None, None

    _default_data = {
        "manga_save_dir": r"D:\dir",
        "database_path": r"D:\db.sqlite3",
        "log_file": os.path.join(os.getcwd(), "..", "data", "log.log"),

        "total_scrapper_threads": 1

    def __getattribute__(mcs, item):
        data = mcs._get_data()

        if item in data:
            return data[item]

        elif hasattr(mcs, item):
            return getattr(mcs, item)

        raise AttributeError(f"Settings has no attribute {item}")

class Settings(metaclass=SettingsMeta):


class JsonFileWrapperBase:
    _loaded_data = None

    _file_path = ""
    _default_data = ""

    def _write_default(cls):

    def _write_data(cls, d):
        os.makedirs(os.path.dirname(os.path.abspath(cls._file_path)), exist_ok=True)

            with open(cls._file_path, "w") as f:
                json.dump(d, f)

        except FileNotFoundError as e:

    def _get_data(cls):
        Reads and caches the JSON file, if the file has already been cached then return the cached file, I cache
        the file to avoid users editing the file during execution and causing errors

        # File has not been cached yet
        if cls._loaded_data is None:

        return cls._loaded_data

    def _read_file(cls) -> bool:
        assert cls._loaded_data is None, "_read_file should only be called once per object"

        # File doesn't exist or is not valid
        if not os.path.isfile(cls._file_path):

            with open(cls._file_path, "r") as f:
                data = json.load(f)

                print(f"Log: Loaded {cls._file_path}")

                cls._loaded_data = data

        except FileNotFoundError as e:

            return False

        return True
  • 1
    \$\begingroup\$ Please add your imports. This is especially important since you use JsonFileWrapperBase._get_data. \$\endgroup\$ – Graipher Sep 2 '19 at 15:46
  • 1
    \$\begingroup\$ I don't think the base class is relevant to my question about best practises forwarding values from JSON but I added it to my question neverthless. \$\endgroup\$ – Joshua Nixon Sep 2 '19 at 15:49


Use it. It makes stuff like this:

os.path.join(os.getcwd(), "..", "data", "log.log"),

much nicer. It can also avoid this kind of thing:


namely, OS-specific directory separators. If you ever hope to have this be cross-platform, you'll want to remove your backslashes and use the libraries for path manipulation.

  • \$\begingroup\$ OK. First thing is that the base class is in a different file. i only added due to a comment (I'll make it clearer in my question). Second, the assert is a very last resort I used for debugging, It will never be called. Thirdly, as you can see I use a metaclass as solely overriding __getattribute__ doesn't work for which mcs is standard. \$\endgroup\$ – Joshua Nixon Sep 2 '19 at 16:32
  • \$\begingroup\$ It was an formatting error regarding your assert comment. You haven't actually answered my question regarding forwarding values from JSON. \$\endgroup\$ – Joshua Nixon Sep 2 '19 at 16:40
  • \$\begingroup\$ That's true! And it doesn't matter - refer to codereview.meta.stackexchange.com/questions/5773/… \$\endgroup\$ – Reinderien Sep 2 '19 at 16:43
  • \$\begingroup\$ Ok, thats fair enough. \$\endgroup\$ – Joshua Nixon Sep 2 '19 at 16:45

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.