Completed
Push — master ( bff2ab...5dc771 )
by Satoru
01:07
created

anyconfig.backend.ToStringDumper   A

Complexity

Total Complexity 3

Size/Duplication

Total Lines 31
Duplicated Lines 0 %
Metric Value
dl 0
loc 31
rs 10
wmc 3

2 Methods

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