Completed
Push — master ( f8bf6f...b89757 )
by Satoru
01:01
created

Parser._container_fn()   A

Complexity

Conditions 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
#
2
# Copyright (C) 2012 - 2015 Satoru SATOH <ssato @ redhat.com>
3
# License: MIT
4
#
5
# pylint: disable=unused-argument
6
r"""Abstract implementation of backend modules:
7
8
Backend module must implement a parser class inherits :class:`Parser` or its
9
children classes of this module and override all or some of the methods as
10
needed:
11
12
  - :meth:`load_from_string`: Load config from string
13
  - :meth:`load_from_stream`: Load config from a file or file-like object
14
  - :meth:`load_from_path`: Load config from file of given path
15
  - :meth:`dump_to_string`: Dump config as a string
16
  - :meth:`dump_to_stream`: Dump config to a file or file-like object
17
  - :meth:`dump_to_path`: Dump config to a file of given path
18
19
Changelog:
20
21
.. versionchanged:: 0.8.3
22
23
   - Add `_ordered` membmer and a class method :meth:` ordered to the class
24
     :class:`Parser`.
25
26
.. versionchanged:: 0.2
27
28
   - The methods :meth:`load_impl`, :meth:`dump_impl` are deprecated and
29
     replaced with :meth:`load_from_stream` and :meth:`load_from_path`,
30
     :meth:`dump_to_string` and :meth:`dump_to_path` respectively.
31
"""
32
from __future__ import absolute_import
33
34
import functools
35
import logging
36
import os
37
38
import anyconfig.compat
39
import anyconfig.mdicts
40
import anyconfig.utils
41
42
43
LOGGER = logging.getLogger(__name__)
44
45
46
def mk_opt_args(keys, kwargs):
47
    """
48
    Make optional kwargs valid and optimized for each backend.
49
50
    :param keys: optional argument names
51
    :param kwargs: keyword arguements to process
52
53
    >>> mk_opt_args(("aaa", ), dict(aaa=1, bbb=2))
54
    {'aaa': 1}
55
    >>> mk_opt_args(("aaa", ), dict(bbb=2))
56
    {}
57
    """
58
    return dict((k, kwargs[k]) for k in keys if k in kwargs)
59
60
61
def ensure_outdir_exists(filepath):
62
    """
63
    Make dir to dump `filepath` if that dir does not exist.
64
65
    :param filepath: path of file to dump
66
    """
67
    outdir = os.path.dirname(filepath)
68
69
    if outdir and not os.path.exists(outdir):
70
        LOGGER.debug("Making output dir: %s", outdir)
71
        os.makedirs(outdir)
72
73
74
def to_method(func):
75
    """
76
    Lift :func:`func` to a method; it will be called with the first argument
77
    `self` ignored.
78
79
    :param func: Any callable object
80
    """
81
    @functools.wraps(func)
82
    def wrapper(*args, **kwargs):
83
        """Wrapper function.
84
        """
85
        return func(*args[1:], **kwargs)
86
87
    return wrapper
88
89
90
class Parser(object):
91
    """
92
    Abstract parser to provide basic implementation of some methods, interfaces
93
    and members.
94
    """
95
    _type = None
96
    _priority = 0   # 0 (lowest priority) .. 99  (highest priority)
97
    _extensions = []
98
    _load_opts = []
99
    _dump_opts = []
100
    _open_flags = ('r', 'w')
101
    _ordered = False
102
103
    @classmethod
104
    def type(cls):
105
        """
106
        Parser's type
107
        """
108
        return cls._type
109
110
    @classmethod
111
    def priority(cls):
112
        """
113
        Parser's priority
114
        """
115
        return cls._priority
116
117
    @classmethod
118
    def extensions(cls):
119
        """
120
        File extensions which this parser can process
121
        """
122
        return cls._extensions
123
124
    @classmethod
125
    def ordered(cls):
126
        """
127
        :return: True if parser can keep the order of keys else False.
128
        """
129
        return cls._ordered
130
131
    @classmethod
132
    def ropen(cls, filepath, **kwargs):
133
        """
134
        :param filepath: Path to file to open to read data
135
        """
136
        return open(filepath, cls._open_flags[0], **kwargs)
137
138
    @classmethod
139
    def wopen(cls, filepath, **kwargs):
140
        """
141
        :param filepath: Path to file to open to write data to
142
        """
143
        return open(filepath, cls._open_flags[1], **kwargs)
144
145
    def _load_options(self, **kwargs):
146
        """
147
        Select backend specific loading options from `kwargs` only.
148
        """
149
        return mk_opt_args(self._load_opts, kwargs)
150
151
    def _container_fn(self, **options):
152
        """
153
        :param options: Keyword options may contain 'ac_ordered'.
154
        :return: Factory (class or function) to make an container.
155
        """
156
        if self.ordered() and options.get("ac_ordered", False):
157
            return anyconfig.compat.OrderedDict
158
        else:
159
            return dict
160
161
    def load_from_string(self, content, to_container, **kwargs):
162
        """
163
        Load config from given string `content`.
164
165
        :param content: Config content string
166
        :param to_container: callble to make a container object later
167
        :param kwargs: optional keyword parameters to be sanitized :: dict
168
169
        :return: Dict-like object holding config parameters
170
        """
171
        return to_container()
172
173
    def load_from_path(self, filepath, to_container, **kwargs):
174
        """
175
        Load config from given file path `filepath`.
176
177
        :param filepath: Config file path
178
        :param to_container: callble to make a container object later
179
        :param kwargs: optional keyword parameters to be sanitized :: dict
180
181
        :return: Dict-like object holding config parameters
182
        """
183
        return to_container()
184
185
    def load_from_stream(self, stream, to_container, **kwargs):
186
        """
187
        Load config from given file like object `stream`.
188
189
        :param stream:  Config file or file like object
190
        :param to_container: callble to make a container object later
191
        :param kwargs: optional keyword parameters to be sanitized :: dict
192
193
        :return: Dict-like object holding config parameters
194
        """
195
        return to_container()
196
197
    def loads(self, content, **options):
198
        """
199
        Load config from given string `content` after some checks.
200
201
        :param content:  Config file content
202
        :param options:
203
            options will be passed to backend specific loading functions.
204
            please note that options have to be sanitized w/ mk_opt_args later
205
            to filter out options not  in _load_opts.
206
207
        :return: dict or dict-like object holding configurations
208
        """
209
        to_container = self._container_fn(**options)
210
        if not content or content is None:
211
            return to_container()
212
213
        return self.load_from_string(content, to_container,
214
                                     **self._load_options(**options))
215
216
    def load(self, path_or_stream, ignore_missing=False, **options):
217
        """
218
        Load config from a file path or a file / file-like object
219
        `path_or_stream` after some checks.
220
221
        :param path_or_stream: Config file path or file{,-like} object
222
        :param ignore_missing:
223
            Ignore and just return None if given `path_or_stream` is not a file
224
            / file-like object (thus, it should be a file path) and does not
225
            exist in actual.
226
        :param options:
227
            options will be passed to backend specific loading functions.
228
            please note that options have to be sanitized w/ mk_opt_args later
229
            to filter out options not  in _load_opts.
230
231
        :return: dict or dict-like object holding configurations
232
        """
233
        to_container = self._container_fn(**options)
234
        options = self._load_options(**options)
235
236
        if isinstance(path_or_stream, anyconfig.compat.STR_TYPES):
237
            if ignore_missing and not os.path.exists(path_or_stream):
238
                return to_container()
239
240
            cnf = self.load_from_path(path_or_stream, to_container, **options)
241
        else:
242
            cnf = self.load_from_stream(path_or_stream, to_container,
243
                                        **options)
244
245
        return cnf
246
247
    def dump_to_string(self, cnf, **kwargs):
248
        """
249
        Dump config `cnf` to a string.
250
251
        :param cnf: Configuration data to dump
252
        :param kwargs: optional keyword parameters to be sanitized :: dict
253
254
        :return: string represents the configuration
255
        """
256
        pass
257
258
    def dump_to_path(self, cnf, filepath, **kwargs):
259
        """
260
        Dump config `cnf` to a file `filepath`.
261
262
        :param cnf: Configuration data to dump
263
        :param filepath: Config file path
264
        :param kwargs: optional keyword parameters to be sanitized :: dict
265
        """
266
        pass
267
268
    def dump_to_stream(self, cnf, stream, **kwargs):
269
        """
270
        Dump config `cnf` to a file-like object `stream`.
271
272
        TODO: How to process socket objects same as file objects ?
273
274
        :param cnf: Configuration data to dump
275
        :param stream:  Config file or file like object
276
        :param kwargs: optional keyword parameters to be sanitized :: dict
277
        """
278
        pass
279
280
    def dumps(self, cnf, **kwargs):
281
        """
282
        Dump config `cnf` to a string.
283
284
        :param cnf: Configuration data to dump
285
        :param kwargs: optional keyword parameters to be sanitized :: dict
286
287
        :return: string represents the configuration
288
        """
289
        cnf = anyconfig.mdicts.convert_to(cnf, **kwargs)
290
        kwargs = mk_opt_args(self._dump_opts, kwargs)
291
        return self.dump_to_string(cnf, **kwargs)
292
293
    def dump(self, cnf, path_or_stream, **kwargs):
294
        """
295
        Dump config `cnf` to a filepath or file-like object
296
        `path_or_stream`.
297
298
        :param cnf: Configuration data to dump
299
        :param path_or_stream: Config file path or file{,-like} object
300
        :param kwargs: optional keyword parameters to be sanitized :: dict
301
        :raises IOError, OSError, AttributeError: When dump failed.
302
        """
303
        cnf = anyconfig.mdicts.convert_to(cnf, **kwargs)
304
        kwargs = mk_opt_args(self._dump_opts, kwargs)
305
306
        if isinstance(path_or_stream, anyconfig.compat.STR_TYPES):
307
            ensure_outdir_exists(path_or_stream)
308
            self.dump_to_path(cnf, path_or_stream, **kwargs)
309
        else:
310
            self.dump_to_stream(cnf, path_or_stream, **kwargs)
311
312
313
class FromStringLoader(Parser):
314
    """
315
    Abstract config parser provides a method to load configuration from string
316
    content to help implement parser of which backend lacks of such function.
317
318
    Parser classes inherit this class have to override the method
319
    :meth:`load_from_string` at least.
320
    """
321
    def load_from_stream(self, stream, to_container, **kwargs):
322
        """
323
        Load config from given stream `stream`.
324
325
        :param stream: Config file or file-like object
326
        :param to_container: callble to make a container object later
327
        :param kwargs: optional keyword parameters to be sanitized :: dict
328
329
        :return: Dict-like object holding config parameters
330
        """
331
        return self.load_from_string(stream.read(), to_container, **kwargs)
332
333
    def load_from_path(self, filepath, to_container, **kwargs):
334
        """
335
        Load config from given file path `filepath`.
336
337
        :param filepath: Config file path
338
        :param to_container: callble to make a container object later
339
        :param kwargs: optional keyword parameters to be sanitized :: dict
340
341
        :return: Dict-like object holding config parameters
342
        """
343
        return self.load_from_stream(self.ropen(filepath), to_container,
344
                                     **kwargs)
345
346
347
class FromStreamLoader(Parser):
348
    """
349
    Abstract config parser provides a method to load configuration from string
350
    content to help implement parser of which backend lacks of such function.
351
352
    Parser classes inherit this class have to override the method
353
    :meth:`load_from_stream` at least.
354
    """
355
    def load_from_string(self, content, to_container, **kwargs):
356
        """
357
        Load config from given string `cnf_content`.
358
359
        :param content: Config content string
360
        :param to_container: callble to make a container object later
361
        :param kwargs: optional keyword parameters to be sanitized :: dict
362
363
        :return: Dict-like object holding config parameters
364
        """
365
        return self.load_from_stream(anyconfig.compat.StringIO(content),
366
                                     to_container, **kwargs)
367
368
    def load_from_path(self, filepath, to_container, **kwargs):
369
        """
370
        Load config from given file path `filepath`.
371
372
        :param filepath: Config file path
373
        :param to_container: callble to make a container object later
374
        :param kwargs: optional keyword parameters to be sanitized :: dict
375
376
        :return: Dict-like object holding config parameters
377
        """
378
        return self.load_from_stream(self.ropen(filepath), to_container,
379
                                     **kwargs)
380
381
382
class ToStringDumper(Parser):
383
    """
384
    Abstract config parser provides a method to dump configuration to a file or
385
    file-like object (stream) and a file of given path to help implement parser
386
    of which backend lacks of such functions.
387
388
    Parser classes inherit this class have to override the method
389
    :meth:`dump_to_string` at least.
390
    """
391
    def dump_to_path(self, cnf, filepath, **kwargs):
392
        """
393
        Dump config `cnf` to a file `filepath`.
394
395
        :param cnf: Configuration data to dump
396
        :param filepath: Config file path
397
        :param kwargs: optional keyword parameters to be sanitized :: dict
398
        """
399
        with self.wopen(filepath) as out:
400
            out.write(self.dump_to_string(cnf, **kwargs))
401
402
    def dump_to_stream(self, cnf, stream, **kwargs):
403
        """
404
        Dump config `cnf` to a file-like object `stream`.
405
406
        TODO: How to process socket objects same as file objects ?
407
408
        :param cnf: Configuration data to dump
409
        :param stream:  Config file or file like object
410
        :param kwargs: optional keyword parameters to be sanitized :: dict
411
        """
412
        stream.write(self.dump_to_string(cnf, **kwargs))
413
414
415
class ToStreamDumper(Parser):
416
    """
417
    Abstract config parser provides methods to dump configuration to a string
418
    content or a file of given path to help implement parser of which backend
419
    lacks of such functions.
420
421
    Parser classes inherit this class have to override the method
422
    :meth:`dump_to_stream` at least.
423
    """
424
    to_stream = to_method(anyconfig.compat.StringIO)
425
426
    def dump_to_string(self, cnf, **kwargs):
427
        """
428
        Dump config `cnf` to a string.
429
430
        :param cnf: Configuration data to dump
431
        :param kwargs: optional keyword parameters to be sanitized :: dict
432
433
        :return: Dict-like object holding config parameters
434
        """
435
        stream = self.to_stream()
436
        self.dump_to_stream(cnf, stream, **kwargs)
437
        return stream.getvalue()
438
439
    def dump_to_path(self, cnf, filepath, **kwargs):
440
        """
441
        Dump config `cnf` to a file `filepath`.
442
443
        :param cnf: Configuration data to dump
444
        :param filepath: Config file path
445
        :param kwargs: optional keyword parameters to be sanitized :: dict
446
        """
447
        with self.wopen(filepath) as out:
448
            self.dump_to_stream(cnf, out, **kwargs)
449
450
# vim:sw=4:ts=4:et:
451