source: trunk/src/allmydata/util/configutil.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 6.0 KB
Line 
1"""
2Read/write config files.
3
4Configuration is returned as Unicode strings.
5
6Ported to Python 3.
7"""
8
9from configparser import ConfigParser
10
11import attr
12
13from twisted.python.runtime import (
14    platform,
15)
16
17
18class UnknownConfigError(Exception):
19    """
20    An unknown config item was found.
21
22    This is possibly raised by validate_config()
23    """
24
25
26def get_config(tahoe_cfg):
27    """Load the config, returning a ConfigParser.
28
29    Configuration is returned as Unicode strings.
30    """
31    # Byte Order Mark is an optional garbage code point you sometimes get at
32    # the start of UTF-8 encoded files. Especially on Windows. Skip it by using
33    # utf-8-sig. https://en.wikipedia.org/wiki/Byte_order_mark
34    with open(tahoe_cfg, "r", encoding="utf-8-sig") as f:
35        cfg_string = f.read()
36    return get_config_from_string(cfg_string)
37
38
39def get_config_from_string(tahoe_cfg_string):
40    """Load the config from a string, return the ConfigParser.
41
42    Configuration is returned as Unicode strings.
43    """
44    parser = ConfigParser(strict=False)
45    parser.read_string(tahoe_cfg_string)
46    return parser
47
48
49def set_config(config, section, option, value):
50    if not config.has_section(section):
51        config.add_section(section)
52    config.set(section, option, value)
53    assert config.get(section, option) == value
54
55def write_config(tahoe_cfg, config):
56    """
57    Write a configuration to a file.
58
59    :param FilePath tahoe_cfg: The path to which to write the
60        config. The directories are created if they do not already exist.
61
62    :param ConfigParser config: The configuration to write.
63
64    :return: ``None``
65    """
66    tmp = tahoe_cfg.temporarySibling()
67    tahoe_cfg.parent().makedirs(ignoreExistingDirectory=True)
68    # FilePath.open can only open files in binary mode which does not work
69    # with ConfigParser.write.
70    with open(tmp.path, "wt") as fp:
71        config.write(fp)
72    # Windows doesn't have atomic overwrite semantics for moveTo.  Thus we end
73    # up slightly less than atomic.
74    if platform.isWindows():
75        try:
76            tahoe_cfg.remove()
77        except FileNotFoundError:
78            pass
79    tmp.moveTo(tahoe_cfg)
80
81def validate_config(fname, cfg, valid_config):
82    """
83    :param ValidConfiguration valid_config: The definition of a valid
84        configuration.
85
86    :raises UnknownConfigError: if there are any unknown sections or config
87        values.
88    """
89    for section in cfg.sections():
90        if not valid_config.is_valid_section(section):
91            raise UnknownConfigError(
92                "'{fname}' contains unknown section [{section}]".format(
93                    fname=fname,
94                    section=section,
95                )
96            )
97        for option in cfg.options(section):
98            if not valid_config.is_valid_item(section, option):
99                raise UnknownConfigError(
100                    "'{fname}' section [{section}] contains unknown option '{option}'".format(
101                        fname=fname,
102                        section=section,
103                        option=option,
104                    )
105                )
106
107
108@attr.s
109class ValidConfiguration(object):
110    """
111    :ivar dict[bytes, tuple[bytes]] _static_valid_sections: A mapping from
112        valid section names to valid items in those sections.
113
114    :ivar _is_valid_section: A callable which accepts a section name as bytes
115        and returns True if that section name is valid, False otherwise.
116
117    :ivar _is_valid_item: A callable which accepts a section name as bytes and
118        an item name as bytes and returns True if that section, item pair is
119        valid, False otherwise.
120    """
121    _static_valid_sections = attr.ib(
122        validator=attr.validators.instance_of(dict)
123    )
124    _is_valid_section = attr.ib(default=lambda section_name: False)
125    _is_valid_item = attr.ib(default=lambda section_name, item_name: False)
126
127    @classmethod
128    def everything(cls):
129        """
130        Create a validator which considers everything valid.
131        """
132        return cls(
133            {},
134            lambda section_name: True,
135            lambda section_name, item_name: True,
136        )
137
138    @classmethod
139    def nothing(cls):
140        """
141        Create a validator which considers nothing valid.
142        """
143        return cls(
144            {},
145            lambda section_name: False,
146            lambda section_name, item_name: False,
147        )
148
149    def is_valid_section(self, section_name):
150        """
151        :return: True if the given section name is valid, False otherwise.
152        """
153        return (
154            section_name in self._static_valid_sections or
155            self._is_valid_section(section_name)
156        )
157
158    def is_valid_item(self, section_name, item_name):
159        """
160        :return: True if the given section name, item_name pair is valid, False
161            otherwise.
162        """
163        return (
164            item_name in self._static_valid_sections.get(section_name, ()) or
165            self._is_valid_item(section_name, item_name)
166        )
167
168
169    def update(self, valid_config):
170        static_valid_sections = self._static_valid_sections.copy()
171        static_valid_sections.update(valid_config._static_valid_sections)
172        return ValidConfiguration(
173            static_valid_sections,
174            _either(self._is_valid_section, valid_config._is_valid_section),
175            _either(self._is_valid_item, valid_config._is_valid_item),
176        )
177
178
179def copy_config(old):
180    """
181    Return a brand new ``ConfigParser`` containing the same values as
182    the given object.
183
184    :param ConfigParser old: The configuration to copy.
185
186    :return ConfigParser: The new object containing the same configuration.
187    """
188    new = ConfigParser()
189    for section_name in old.sections():
190        new.add_section(section_name)
191        for k, v in old.items(section_name):
192            new.set(section_name, k, v.replace("%", "%%"))
193    return new
194
195
196def _either(f, g):
197    """
198    :return: A function which returns True if either f or g returns True.
199    """
200    return lambda *a, **kw: f(*a, **kw) or g(*a, **kw)
Note: See TracBrowser for help on using the repository browser.