File: config_tools.py

package info (click to toggle)
python-easydev 0.13.3%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 636 kB
  • sloc: python: 1,904; makefile: 116; javascript: 49
file content (359 lines) | stat: -rw-r--r-- 11,673 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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# -*- python -*-
# -*- coding: utf-8 -*-
#
#  This file is part of the easydev software
#
#  Copyright (c) 2011-2024
#
#  File author(s): Thomas Cokelaer <cokelaer@gmail.com>
#
#  Distributed under BSD3 license
#
#  Website: https://github.com/cokelaer/easydev
#  Documentation: http://packages.python.org/easydev
#
##############################################################################
import os
from configparser import ConfigParser

__all__ = ["CustomConfig", "DynamicConfigParser"]


class _DictSection:
    """Dictionary section.

    Reference: https://gist.github.com/dangoakachan/3855920

    """

    def __init__(self, config, section):
        object.__setattr__(self, "_config", config)
        object.__setattr__(self, "_section", section)

    def __getattr__(self, attr):
        return self.get(attr, None)

    __getitem__ = __getattr__

    def get(self, attr, default=None):
        if attr in self:
            return self._config.get(self._section, attr)
        else:  # pragma: no cover
            return default

    def __setattr__(self, attr, value):
        if attr.startswith("_"):
            object.__setattr__(self, attr, value)
        else:  # pragma: no cover
            self.__setitem__(attr, value)

    def __setitem__(self, attr, value):
        if self._section not in self._config:  # pragma: no cover
            self._config.add_section(self._section)

        self._config.set(self._section, attr, str(value))

    def __delattr__(self, attr):
        if attr in self:
            self._config.remove_option(self._section, attr)

    __delitem__ = __delattr__

    def __contains__(self, attr):
        config = self._config
        section = self._section

        return config.has_section(section) and config.has_option(section, attr)


class DynamicConfigParser(ConfigParser):
    """Enhanced version of Config Parser

    Provide some aliases to the original ConfigParser class and
    new methods such as :meth:`save` to save the config object in a file.

    .. code-block:: python

        >>> from easydev.config_tools import ConfigExample
        >>> standard_config_file = ConfigExample().config
        >>> c = DynamicConfigParser(standard_config_file)
        >>>
        >>> # then to get the sections, simply type as you would normally do with ConfigParser
        >>> c.sections()
        >>> # or for the options of a specific sections:
        >>> c.get_options('General')

    You can now also directly access to an option as follows::

        c.General.tag

    Then, you can add or remove sections (:meth:`remove_section`, :meth:`add_section`),
    or option from a section :meth:`remove_option`. You can save the instance into a file
    or print it::

        print(c)

    .. warning:: if you set options manually (e.G. self.GA.test =1 if GA is a
        section and test one of its options), then the save/write does not work
        at the moment even though if you typoe self.GA.test, it has the correct value


    Methods inherited from ConfigParser are available:

    ::

        # set value of an option in a section
        c.set(section, option, value=None)
        # but with this class, you can also use the attribute
        c.section.option = value

        # set value of an option in a section
        c.remove_option(section, option)
        c.remove_section(section)


    """

    def __init__(self, config_or_filename=None, *args, **kargs):

        object.__setattr__(self, "_filename", config_or_filename)
        # why not a super usage here ? Maybe there were issues related
        # to old style class ?
        ConfigParser.__init__(self, *args, **kargs)

        if isinstance(self._filename, str) and os.path.isfile(self._filename):
            self.read(self._filename)
        elif isinstance(config_or_filename, ConfigParser):
            self._replace_config(config_or_filename)
        elif config_or_filename == None:
            pass
        else:
            raise TypeError("config_or_filename must be a valid filename or valid ConfigParser instance")

    def read(self, filename):
        """Load a new config from a filename (remove all previous sections)"""
        if os.path.isfile(filename) == False:
            raise IOError("filename {0} not found".format(filename))

        config = ConfigParser()
        config.read(filename)

        self._replace_config(config)

    def _replace_config(self, config):
        """Remove all sections and add those from the input config file

        :param config:

        """
        for section in self.sections():
            self.remove_section(section)

        for section in config.sections():
            self.add_section(section)
            for option in config.options(section):
                data = config.get(section, option)
                self.set(section, option, data)

    def get_options(self, section):
        """Alias to get all options of a section in a dictionary

        One would normally need to extra each option manually::

            for option in config.options(section):
                config.get(section, option, raw=True)#

        then, populate a dictionary and finally take care of the types.

        .. warning:: types may be changed .For instance the string "True"
            is interpreted as a True boolean.

        ..  seealso:: internally, this method uses :meth:`section2dict`
        """
        return self.section2dict(section)

    def section2dict(self, section):
        """utility that extract options of a ConfigParser section into a dictionary

        :param ConfigParser config: a ConfigParser instance
        :param str section: the section to extract

        :returns: a dictionary where key/value contains all the
            options/values of the section required

        Let us build up  a standard config file:
        .. code-block:: python

            >>> # Python 3 code
            >>> from configparser import ConfigParser
            >>> c = ConfigParser()
            >>> c.add_section('general')
            >>> c.set('general', 'step', str(1))
            >>> c.set('general', 'verbose', 'True')

        To access to the step options, you would write::

            >>> c.get('general', 'step')

        this function returns a dictionary that may be manipulated as follows::

            >>> d_dict.general.step

        .. note:: a value (string) found to be True, Yes, true,
            yes is transformed to True
        .. note:: a value (string) found to be False, No, no,
            false is transformed to False
        .. note:: a value (string) found to be None; none,
            "" (empty string) is set to None
        .. note:: an integer is cast into an int
        """
        options = {}
        for option in self.options(section):  # pragma no cover
            data = self.get(section, option, raw=True)
            if data.lower() in ["true", "yes"]:
                options[option] = True
            elif data.lower() in ["false", "no"]:
                options[option] = False
            elif data in ["None", None, "none", ""]:
                options[option] = None
            else:
                try:  # numbers
                    try:
                        options[option] = self.getint(section, option)
                    except:
                        options[option] = self.getfloat(section, option)
                except:  # string
                    options[option] = self.get(section, option, raw=True)
        return options

    def save(self, filename):
        """Save all sections/options to a file.

        :param str filename: a valid filename

        ::

            config = ConfigParams('config.ini') #doctest: +SKIP
            config.save('config2.ini') #doctest: +SKIP

        """
        try:
            if os.path.exists(filename) == True:
                print("Warning: over-writing %s " % filename)
            fp = open(filename, "w")
        except Exception as err:  # pragma: no cover
            print(err)
            raise Exception("filename could not be opened")

        self.write(fp)
        fp.close()

    def add_option(self, section, option, value=None):
        """add an option to an existing section (with a value)

        .. code-block:: python

            >>> c = DynamicConfigParser()
            >>> c.add_section("general")
            >>> c.add_option("general", "verbose", True)
        """
        assert section in self.sections(), "unknown section"
        # TODO I had to cast to str with DictSection
        self.set(section, option, value=str(value))

    def __str__(self):
        str_ = ""
        for section in self.sections():
            str_ += "[" + section + "]\n"
            for option in self.options(section):
                data = self.get(section, option, raw=True)
                str_ += option + " = " + str(data) + "\n"
            str_ += "\n\n"

        return str_

    def __getattr__(self, key):
        return _DictSection(self, key)

    __getitem__ = __getattr__

    def __setattr__(self, attr, value):
        if attr.startswith("_") or attr:
            object.__setattr__(self, attr, value)
        else:  # pragma: no cover
            self.__setitem__(attr, value)

    # def __setitem__(self, attr, value):
    #    if isinstance(value, dict):
    #        section = self[attr]
    #        for k, v in value.items():
    #            section[k] = v
    #    else:
    #        raise TypeError("value must be a valid dictionary")

    def __delattr__(self, attr):
        if attr in self:
            self.remove_section(attr)

    def __contains__(self, attr):
        return self.has_section(attr)

    def __eq__(self, data):
        # FIXME if you read file, the string "True" is a string
        # but you may want it to be converted to a True boolean value
        if sorted(data.sections()) != sorted(self.sections()):
            print("Sections differ")
            return False
        for section in self.sections():

            for option in self.options(section):
                try:
                    if str(self.get(section, option, raw=True)) != str(data.get(section, option, raw=True)):
                        print("option %s in section %s differ" % (option, section))
                        return False
                except:  # pragma: no cover
                    return False
        return True


class CustomConfig(object):
    """Base class to manipulate a config directory"""

    def __init__(self, name, verbose=False):
        self.verbose = verbose
        from easydev import appdirs

        self.appdirs = appdirs.AppDirs(name)

    def init(self):
        sdir = self.appdirs.user_config_dir
        self._get_and_create(sdir)

    def _get_config_dir(self):
        sdir = self.appdirs.user_config_dir
        return self._get_and_create(sdir)

    user_config_dir = property(_get_config_dir, doc="return directory of this configuration file")

    def _get_and_create(self, sdir):
        if not os.path.exists(sdir):
            print("Creating directory %s " % sdir)
            try:
                self._mkdirs(sdir)
            except Exception:  # pragma: no cover
                print("Could not create the path %s " % sdir)
                return None
        return sdir

    def _mkdirs(self, newdir, mode=0o777):
        """See :func:`easydev.tools.mkdirs`"""
        from easydev.tools import mkdirs

        mkdirs(newdir, mode)

    def remove(self):
        try:
            sdir = self.appdirs.user_config_dir
            os.rmdir(sdir)
        except Exception as err:  # pragma: no cover
            raise Exception(err)