File: toml_loader.py

package info (click to toggle)
python-dynaconf 3.2.12-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,900 kB
  • sloc: python: 21,464; sh: 9; makefile: 4
file content (138 lines) | stat: -rw-r--r-- 4,629 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations

import warnings
from pathlib import Path

from dynaconf import default_settings
from dynaconf.constants import TOML_EXTENSIONS
from dynaconf.loaders.base import BaseLoader
from dynaconf.loaders.base import SourceMetadata
from dynaconf.utils import object_merge
from dynaconf.vendor import toml  # Backwards compatibility with uiri/toml
from dynaconf.vendor import tomllib  # New tomllib stdlib on py3.11


def load(
    obj,
    env=None,
    silent=True,
    key=None,
    filename=None,
    validate=False,
    identifier="toml",
):
    """
    Reads and loads in to "obj" a single key or all keys from source file.

    :param obj: the settings instance
    :param env: settings current env default='development'
    :param silent: if errors should raise
    :param key: if defined load a single key, else load all in env
    :param filename: Optional custom filename to load
    :return: None
    """
    # when load_file function is called directly it comes with module and line number
    if isinstance(identifier, SourceMetadata) and identifier.loader.startswith(
        "load_file"
    ):
        identifier = identifier.loader

    try:
        loader = BaseLoader(
            obj=obj,
            env=env,
            identifier=identifier,
            extensions=TOML_EXTENSIONS,
            file_reader=tomllib.load,
            string_reader=tomllib.loads,
            opener_params={"mode": "rb"},
            validate=validate,
        )
        loader.load(
            filename=filename,
            key=key,
            silent=silent,
        )
    except UnicodeDecodeError:  # pragma: no cover
        """
        NOTE: Compat functions exists to keep backwards compatibility with
        the new tomllib library. The old library was called `toml` and
        the new one is called `tomllib`.

        The old lib uiri/toml allowed unicode characters and re-added files
        as string.

        The new tomllib (stdlib) does not allow unicode characters, only
        utf-8 encoded, and read files as binary.

        NOTE: In dynaconf 4.0.0 we will drop support for the old library
        removing the compat functions and calling directly the new lib.
        """
        loader = BaseLoader(
            obj=obj,
            env=env,
            identifier=identifier,
            extensions=TOML_EXTENSIONS,
            file_reader=toml.load,
            string_reader=toml.loads,
            validate=validate,
        )
        loader.load(
            filename=filename,
            key=key,
            silent=silent,
        )

        warnings.warn(
            "TOML files should have only UTF-8 encoded characters. "
            "starting on 4.0.0 dynaconf will stop allowing invalid chars.",
        )


def write(settings_path, settings_data, merge=True):
    """Write data to a settings file.

    :param settings_path: the filepath
    :param settings_data: a dictionary with data
    :param merge: boolean if existing file should be merged with new data
    """
    settings_path = Path(settings_path)
    if settings_path.exists() and merge:  # pragma: no cover
        try:  # tomllib first
            with open(str(settings_path), "rb") as open_file:
                object_merge(tomllib.load(open_file), settings_data)
        except UnicodeDecodeError:  # pragma: no cover
            # uiri/toml fallback (TBR on 4.0.0)
            with open(
                str(settings_path),
                encoding=default_settings.ENCODING_FOR_DYNACONF,
            ) as open_file:
                object_merge(toml.load(open_file), settings_data)

    try:  # tomllib first
        with open(str(settings_path), "wb") as open_file:
            tomllib.dump(encode_nulls(settings_data), open_file)
    except UnicodeEncodeError:  # pragma: no cover
        # uiri/toml fallback (TBR on 4.0.0)
        with open(
            str(settings_path),
            "w",
            encoding=default_settings.ENCODING_FOR_DYNACONF,
        ) as open_file:
            toml.dump(encode_nulls(settings_data), open_file)

        warnings.warn(
            "TOML files should have only UTF-8 encoded characters. "
            "starting on 4.0.0 dynaconf will stop allowing invalid chars.",
        )


def encode_nulls(data):
    """TOML does not support `None` so this function transforms to '@none '."""
    if data is None:
        return "@none "
    if isinstance(data, dict):
        return {key: encode_nulls(value) for key, value in data.items()}
    elif isinstance(data, (list, tuple)):
        return [encode_nulls(item) for item in data]
    return data