Statistics
| Branch: | Tag: | Revision:

root / snf-common / synnefo / settings / setup.py @ e2c178f2

History | View | Annotate | Download (25.6 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from collections import defaultdict
35
from pprint import pformat
36
from textwrap import wrap
37
from itertools import chain
38
from os import environ
39

    
40

    
41
class Setting(object):
42
    """Setting is the parent class of all setting annotations.
43

44
    Setting.initialize_settings() will register a dictionary
45
    of setting names to Setting instances, process all
46
    annotations.
47

48
    Setting.load_configuration() will load user-specific
49
    configurations to the defaults.
50
    A setting set in this stage will be flagged 'configured'.
51
    Those that are not set in this stage will be flagged 'default'.
52

53
    Setting.configure_settings() will post-process all settings,
54
    maintaining the various registries and calling configuration
55
    callbacks for auto-generation or validation.
56

57
    A setting has the following attributes:
58

59
    ** CONFIGURATION ATTRIBUTES **
60

61
    default_value:
62
        The default value to be assigned if not given any by the
63
        administrator in the config files. To omit a default value
64
        assign Setting.NoValue to it.
65

66
    example_value:
67
        A value to serve as an informative guide for those who
68
        want to configure the setting. The default_value might not
69
        be that informative.
70

71
    description:
72
        Setting description for administrators and developers.
73

74
    category:
75
        An all-lowercase category name for grouping settings together.
76

77
    dependencies:
78
        A list of setting names and categories whose values will be
79
        given as input to configure_callback.
80

81
    configure_callback:
82
        A function that accepts three arguments, a Setting instance,
83
        the corresponding setting value given by the configuration
84
        files, and a dictionary with all setting dependencies declared.
85
        If the setting value has not be set by a config file,
86
        then its value (second argument) will be Setting.NoValue.
87

88
        If no value was provided by a config file, the callback must
89
        return the final setting value which will be assigned to it.
90
        If a value was provided, the callback must return
91
        Setting.NoValue to acknowledge it.
92

93
    export:
94
        If export is false then the setting will be marked not to be
95
        advertised in user-friendly lists or files.
96

97
    ** STATE ATTRIBUTES **
98

99
    setting_name:
100
        This is the name this setting annotation was given to.
101
        Initialized as None. Settings.initialize_settings will set it.
102

103
    configured_value:
104
        This is the value given by a configuration file.
105
        If it does not exist, then the setting was not set by
106
        the administrator.
107

108
        Initialized as Setting.NoValue.
109
        Settings.load_configuration will set it.
110

111
    configured_source:
112
        This is the source (e.g. configuration file path) where the
113
        setting was configured. It is None if the setting has not
114
        been configured.
115

116
    configured_depth:
117
        The depth of the setting in the dependency tree (forest).
118

119
    runtime_value:
120
        This is the final setting value after all configuration
121
        processing. Its existence indicates that all processing
122
        for this setting has been completed.
123

124
        Initialized as Setting.NoValue.
125
        Settings.configure_one_setting will set it upon completion.
126

127
    serial:
128
        The setting's serial index in the 'registry' catalog.
129
        Represents the chronological order of execution of its annotation.
130

131
    dependents:
132
        A list of the names of the settings that depend on this one.
133

134
    fail_exception:
135
        If the configuration of a setting has failed, this holds
136
        the exception raised, marking it as failed to prevent
137
        further attempts, and to be able to re-raise the error.
138
        Initialized as None.
139

140
    Subclasses may expose any subset of the above, as well as additional
141
    attributes of their own.
142

143
    Setting will construct certain setting catalogs during runtime
144
    initialization, later accessible via a 'Catalogs' class attribute
145
    dictionary:
146

147
        catalog = Setting.Catalogs['catalog_name']
148

149
    The catalogs are:
150

151
    1. Catalog 'settings'
152

153
       A catalog of all setting annotations collected at initialization.
154
       This is useful to access setting annotations at runtime,
155
       for example a setting's default value:
156

157
       settings = Setting.Catalogs['settings']
158
       settings['SOCKET_CONNECT_RETRIES'].default_value
159

160
    2. Catalog 'types'
161

162
       A catalog of the different types of settings.
163
       At each Setting instantiation, the (sub)class attribute
164
       'setting_type' will be used as the setting type name in the
165
       catalog.  Each entry in the catalog is a dictionary of setting
166
       names and setting annotation instances. For example:
167
       
168
       setting_types = Setting.Catalogs['types']
169
       setting_types['mandatory']['SETTING_NAME'] == setting_instance
170

171
    3. Catalog 'categories'
172

173
       A catalog of the different setting categories.
174
       At each Setting instantiation, the instance attribute 'category'
175
       will be used to group settings in categories. For example:
176

177
       categories = Setting.Catalogs['categories']
178
       categories['django']['SETTING_NAME'] == setting_instance
179

180
    4. Catalog 'defaults'
181

182
       A catalog of all settings that have been initialized and their
183
       default, pre-configuration values. This catalog is useful as
184
       input to the configuration from files.
185

186
    5. Catalog 'configured'
187

188
       A catalog of all settings that were configured by the
189
       administrator in the configuration files. Very relevant
190
       to the administrator as it effectively represents the
191
       deployment-specific configuration, without noise from
192
       all settings left default.
193
       Each setting is registered in the catalog just before
194
       the configure_callback is called.
195
       The catalog values are final setting values, not instances.
196

197
       configured = Setting.Catalogs['configured']
198
       print '\n'.join(("%s = %s" % it) for it in configured.items())
199

200
    5. Catalog 'runtime'
201

202
        A catalog of all finalized settings and their runtime values.
203
        This is output of the setting configuration process and
204
        where setting values must be read from at runtime.
205

206
    """
207

    
208
    class SettingsError(Exception):
209
        pass
210

    
211
    NoValue = type('NoValue', (), {})
212

    
213
    setting_type = 'setting'
214
    _serial = 0
215

    
216
    Catalogs = {
217
        'registry': {},
218
        'settings': {},
219
        'types': defaultdict(dict),
220
        'categories': defaultdict(dict),
221
        'defaults': {},
222
        'configured': {},
223
        'runtime': {},
224
    }
225

    
226
    default_value = None
227
    example_value = None
228
    description = 'This setting is missing documentation'
229
    category = 'misc'
230
    dependencies = ()
231
    dependents = ()
232
    export = True
233

    
234
    serial = None
235
    configured_value = NoValue
236
    configured_source = None
237
    configured_depth = 0
238
    runtime_value = NoValue
239
    fail_exception = None
240

    
241
    def __repr__(self):
242
        flags = []
243
        if self.configured_value is not Setting.NoValue:
244
            flags.append("configured")
245
            value = self.configured_value
246
        else:
247
            flags.append("default")
248
            value = self.default_value
249

    
250
        if self.runtime_value is not Setting.NoValue:
251
            flags.append("finalized")
252

    
253
        if self.fail_exception is not None:
254
            flags.append("failed({0})".format(self.fail_exception))
255
        r = "<{setting_type}[{flags}]: {value}>" 
256
        r = r.format(setting_type=self.setting_type,
257
                     value=repr(value),
258
                     flags=','.join(flags))
259
        return r
260

    
261
    __str__ = __repr__
262

    
263
    def present_as_comment(self, runtime=False):
264
        header = "# {name}: type {type}, category '{categ}'"
265
        header = header.format(name=self.setting_name,
266
                               type=self.setting_type.upper(),
267
                               categ=self.category)
268
        header = [header]
269

    
270
        if self.dependencies:
271
            header += ["# Depends on: "]
272
            header += ["#     " + d for d in sorted(self.dependencies)]
273

    
274
        description = wrap(self.description, 70)
275
        description = [("# " + s) for s in description]
276

    
277
        example_value = self.example_value
278
        default_value = self.default_value
279
        runtime_value = self.runtime_value
280
        if example_value != default_value:
281
            example = "Example value: {0}"
282
            example = example.format(pformat(example_value)).split('\n')
283
            description += ["# "]
284
            description += [("# " + s) for s in example]
285

    
286
        assignment = "{name} = {value}"
287
        assignment = assignment.format(name=self.setting_name,
288
                                       value=pformat(default_value))
289
        assignment = [("#" + s) for s in assignment.split('\n')]
290
        if runtime and self.configured_value is not Setting.NoValue \
291
                and runtime_value != self.default_value:
292
            runtime_assignment = "{name} = {value}"
293
            runtime_assignment = runtime_assignment.format(
294
                name=self.setting_name,
295
                value=pformat(self.runtime_value))
296
            assignment += [runtime_assignment]
297

    
298
        return '\n'.join(chain(header, ['#'],
299
                               description, ['#'],
300
                               assignment))
301

    
302
    @staticmethod
303
    def configure_callback(setting, value, dependencies):
304
        if value is Setting.NoValue:
305
            return setting.default_value
306
        else:
307
            # by default, acknowledge the configured value
308
            # and allow it to be used.
309
            return Setting.NoValue
310

    
311
    def validate(self):
312
        """Example setting validate method"""
313

    
314
        NoValue = Setting.NoValue
315
        setting_name = self.setting_name
316
        if self is not Setting.Catalogs['settings'][setting_name]:
317
            raise AssertionError()
318

    
319
        runtime_value = self.runtime_value
320
        if runtime_value is NoValue:
321
            raise AssertionError()
322

    
323
        configured_value = self.configured_value
324
        if configured_value not in (NoValue, runtime_value):
325
            raise AssertionError()
326

    
327
    def __init__(self, **kwargs):
328

    
329
        attr_names = ['default_value', 'example_value', 'description',
330
                      'category', 'dependencies', 'configure_callback',
331
                      'export']
332

    
333
        for name in attr_names:
334
            if name in kwargs:
335
                setattr(self, name, kwargs[name])
336

    
337
        serial = Setting._serial
338
        Setting._serial = serial + 1
339
        registry = Setting.Catalogs['registry']
340
        self.serial = serial
341
        registry[serial] = self
342

    
343
    @staticmethod
344
    def is_valid_setting_name(name):
345
        return name.isupper() and not name.startswith('_')
346

    
347
    @staticmethod
348
    def get_settings_from_object(settings_object):
349
        var_list = []
350
        is_valid_setting_name = Setting.is_valid_setting_name
351
        for name in dir(settings_object):
352
            if not is_valid_setting_name(name):
353
                continue
354
            var_list.append((name, getattr(settings_object, name)))
355
        return var_list
356

    
357
    @staticmethod
358
    def initialize_settings(settings_dict, strict=False):
359
        Catalogs = Setting.Catalogs
360
        settings = Catalogs['settings']
361
        categories = Catalogs['categories']
362
        defaults = Catalogs['defaults']
363
        types = Catalogs['types']
364

    
365
        for name, value in settings_dict.iteritems():
366
            if not isinstance(value, Setting):
367
                if strict:
368
                    m = "Setting name '{name}' has non-annotated value '{value}'!"
369
                    m = m.format(name=name, value=value)
370
                    raise Setting.SettingsError(m)
371
                else:
372
                    value = Setting(default_value=value)
373

    
374
            # FIXME: duplicate annotations?
375
            #if name in settings:
376
            #    m = ("Duplicate annotation for setting '{name}': '{value}'. "
377
            #         "Original annotation: '{original}'")
378
            #    m = m.format(name=name, value=value, original=settings[name])
379
            #    raise Setting.SettingsError(m)
380
            value.setting_name = name
381
            settings[name] = value
382
            categories[value.category][name] = value
383
            types[value.setting_type][name] = value
384
            default_value = value.default_value
385
            defaults[name] = default_value
386

    
387
        defaults['_SETTING_CATALOGS'] = Catalogs
388

    
389
    @staticmethod
390
    def load_settings_from_file(path, settings_dict=None):
391
        if settings_dict is None:
392
            settings_dict = {}
393
        new_settings = {}
394
        execfile(path, settings_dict, new_settings)
395
        return new_settings
396

    
397
    @staticmethod
398
    def load_configuration(new_settings,
399
                           source='unknonwn',
400
                           allow_override=False,
401
                           allow_unknown=False,
402
                           allow_known=True):
403

    
404
        settings = Setting.Catalogs['settings']
405
        defaults = Setting.Catalogs['defaults']
406
        configured = Setting.Catalogs['configured']
407
        is_valid_setting_name = Setting.is_valid_setting_name
408

    
409
        for name, value in new_settings.iteritems():
410
            if not is_valid_setting_name(name):
411
                # silently ignore it?
412
                continue
413

    
414
            if name in settings:
415
                if not allow_known:
416
                    m = ("{source}: setting '{name} = {value}' not allowed to "
417
                         "be set here")
418
                    m = m.format(source=source, name=name, value=value)
419
                    raise Setting.SettingsError(m)
420
            else:
421
                if allow_unknown:
422
                    # pretend this was declared in a default settings module
423
                    desc = "Unknown setting from {source}".format(source=source)
424

    
425
                    setting = Setting(default_value=value,
426
                                      category='unknown',
427
                                      description=desc)
428
                    Setting.initialize_settings({name: setting}, strict=True)
429
                else:
430
                    m = ("{source}: unknown setting '{name} = {value}' not "
431
                         "allowed to be set here")
432
                    m = m.format(source=source, name=name, value=value)
433
                    raise Setting.SettingsError(m)
434

    
435
            if not allow_override and name in configured:
436
                m = ("{source}: new setting '{name} = {value}' "
437
                     "overrides setting '{name} = {oldval}'")
438
                m = m.format(source=source, name=name, value=value,
439
                             oldval=defaults[name])
440
                raise Setting.SettingsError(m)
441

    
442
            # setting has been accepted for configuration
443
            setting = settings[name]
444
            setting.configured_value = value
445
            setting.configured_source = source
446
            configured[name] = value
447
            defaults[name] = value
448

    
449
        return new_settings
450

    
451
    @staticmethod
452
    def configure_one_setting(setting_name, dep_stack=()):
453
        dep_stack += (setting_name,)
454
        Catalogs = Setting.Catalogs
455
        settings = Catalogs['settings']
456
        runtime = Catalogs['runtime']
457
        NoValue = Setting.NoValue
458

    
459
        if setting_name not in settings:
460
            m = "Unknown setting '{name}'"
461
            m = m.format(name=setting_name)
462
            raise Setting.SettingsError(m)
463

    
464
        setting = settings[setting_name]
465
        if setting.runtime_value is not NoValue:
466
            # already configured, nothing to do.
467
            return
468

    
469
        if setting.fail_exception is not None:
470
            # it has previously failed, re-raise the error
471
            exc = setting.fail_exception
472
            if not isinstance(exc, Exception):
473
                exc = Setting.SettingsError(str(exc))
474
            raise exc
475

    
476
        setting_value = setting.configured_value
477
        if isinstance(setting_value, Setting):
478
            m = ("Unprocessed setting annotation '{name} = {value}' "
479
                 "in setting configuration stage!")
480
            m = m.format(name=setting_name, value=setting_value)
481
            raise AssertionError(m)
482

    
483
        configure_callback = setting.configure_callback
484
        if not configure_callback:
485
            setting.runtime_value = setting_value
486
            return
487

    
488
        if not callable(configure_callback):
489
            m = ("attribute 'configure_callback' of "
490
                 "'{setting}' is not callable!")
491
            m = m.format(setting=setting)
492
            exc = Setting.SettingsError(m)
493
            setting.fail_exception = exc
494
            raise exc
495

    
496
        deps = {}
497
        for dep_name in setting.dependencies:
498
            if dep_name not in settings:
499
                m = ("Unknown dependecy setting '{dep_name}' "
500
                     "for setting '{name}'!")
501
                m = m.format(dep_name=dep_name, name=setting_name)
502
                raise Setting.SettingsError(m)
503

    
504
            if dep_name in dep_stack:
505
                m = "Settings dependency cycle detected: {stack}"
506
                m = m.format(stack=dep_stack)
507
                exc = Setting.SettingsError(m)
508
                setting.fail_exception = exc
509
                raise exc
510

    
511
            dep_setting = settings[dep_name]
512
            if dep_setting.fail_exception is not None:
513
                m = ("Cannot configure setting {name} because it depends "
514
                     "on '{dep}' which has failed to configure.")
515
                m = m.format(name=setting_name, dep=dep_name)
516
                exc = Setting.SettingsError(m)
517
                setting.fail_exception = exc
518
                raise exc
519

    
520
            if dep_setting.runtime_value is NoValue:
521
                Setting.configure_one_setting(dep_name, dep_stack)
522

    
523
            dep_value = dep_setting.runtime_value
524
            deps[dep_name] = dep_value
525

    
526
        try:
527
            new_value = configure_callback(setting, setting_value, deps)
528
        except Setting.SettingsError as e:
529
            setting.fail_exception = e
530
            raise
531

    
532
        if new_value is not NoValue:
533
            if setting_value is not NoValue:
534
                m = ("Configure callback of setting '{name}' does not "
535
                     "acknowledge the fact that a value '{value}' was "
536
                     "provided by '{source}' and wants to assign "
537
                     "a value '{newval}' anyway!")
538
                m = m.format(name=setting_name, value=setting_value,
539
                             source=setting.configured_source,
540
                             newval=new_value)
541
                exc = Setting.SettingsError(m)
542
                setting.fail_exception = exc
543
                raise exc
544
            else:
545
                setting_value = new_value
546

    
547
        setting.runtime_value = setting_value
548
        runtime[setting_name] = setting_value
549

    
550
    @staticmethod
551
    def configure_settings(setting_names=()):
552
        settings = Setting.Catalogs['settings']
553
        if not setting_names:
554
            setting_names = settings.keys()
555

    
556
        bottom = set(settings.keys())
557
        for name, setting in settings.iteritems():
558
            dependencies = setting.dependencies
559
            if not dependencies:
560
                continue
561
            bottom.discard(name)
562
            for dep_name in setting.dependencies:
563
                dep_setting = settings[dep_name]
564
                if not dep_setting.dependents:
565
                    dep_setting.dependents = []
566
                dep_setting.dependents.append(name)
567

    
568
        depth = 1
569
        while True:
570
            dependents = []
571
            for name in bottom:
572
                setting = settings[name]
573
                setting.configured_depth = depth
574
                dependents.extend(setting.dependents)
575
            if not dependents:
576
                break
577
            bottom = dependents
578
            depth += 1
579

    
580
        failed = []
581
        for name in Setting.Catalogs['settings']:
582
            try:
583
                Setting.configure_one_setting(name)
584
            except Setting.SettingsError as e:
585
                failed.append(e)
586

    
587
        if failed:
588
            import sys
589
            sys.stderr.write('\n')
590
            sys.stderr.write('\n'.join(map(str, failed)))
591
            sys.stderr.write('\n\n')
592
            raise Setting.SettingsError("Failed to configure settings.")
593

    
594
    @staticmethod
595
    def enforce_not_configurable(setting, value, deps=None):
596
        if value is not Setting.NoValue:
597
            m = "Setting '{name}' is not configurable."
598
            m = m.format(name=setting.setting_name)
599
            raise Setting.SettingsError(m)
600
        return setting.default_value
601

    
602

    
603
class Mandatory(Setting):
604
    """Mandatory settings have to be to be configured by the
605
    administrator in the configuration files. There are no defaults,
606
    and not giving a value will raise an exception.
607

608
    """
609
    setting_type = 'mandatory'
610

    
611
    def __init__(self, example_value=Setting.NoValue, **kwargs):
612
        if example_value is Setting.NoValue:
613
            m = "Mandatory settings require an example_value"
614
            raise Setting.SettingsError(m)
615
        kwargs['example_value'] = example_value
616
        kwargs['export'] = True
617
        Setting.__init__(self, **kwargs)
618

    
619
    @staticmethod
620
    def configure_callback(setting, value, deps):
621
        if value is Setting.NoValue:
622
            if environ.get('SYNNEFO_RELAX_MANDATORY_SETTINGS'):
623
                return setting.example_value
624

    
625
            m = ("Setting '{name}' is mandatory. "
626
                 "Please provide a real value. "
627
                 "Example value: '{example}'")
628
            m = m.format(name=setting.setting_name,
629
                         example=setting.example_value)
630
            raise Setting.SettingsError(m)
631

    
632
        return Setting.NoValue
633

    
634

    
635
class Default(Setting):
636
    """Default settings are not mandatory.
637
    There are default values that are meant to work well, and also serve as an
638
    example if no explicit example is given.
639

640
    """
641
    setting_type = 'default'
642

    
643
    def __init__(self, default_value=Setting.NoValue,
644
                 description="No description", **kwargs):
645
        if default_value is Setting.NoValue:
646
            m = "Default settings require a default_value"
647
            raise Setting.SettingsError(m)
648

    
649
        kwargs['default_value'] = default_value
650
        if 'example_value' not in kwargs:
651
            kwargs['example_value'] = default_value
652
        kwargs['description'] = description
653
        Setting.__init__(self, **kwargs)
654

    
655

    
656
class Constant(Setting):
657
    """Constant settings are a like defaults, only they are not intended to be
658
    visible or configurable by the administrator.
659

660
    """
661
    setting_type = 'constant'
662

    
663
    def __init__(self, default_value=Setting.NoValue,
664
                 description="No description", **kwargs):
665
        if default_value is Setting.NoValue:
666
            m = "Constant settings require a default_value"
667
            raise Setting.SettingsError(m)
668

    
669
        kwargs['default_value'] = default_value
670
        if 'example_value' not in kwargs:
671
            kwargs['example_value'] = default_value
672
        kwargs['export'] = False
673
        kwargs['description'] = description
674
        Setting.__init__(self, **kwargs)
675

    
676

    
677
class Auto(Setting):
678
    """Auto settings can be computed automatically.
679
    Administrators may attempt to override them and the setting
680
    may or may not accept being overriden. If override is not accepted
681
    it will result in an error, not in a silent discarding of user input.
682

683
    """
684
    setting_type = 'auto'
685

    
686
    def __init__(self, configure_callback=None, **kwargs):
687
        if not configure_callback:
688
            m = "Auto settings must provide a configure_callback"
689
            raise Setting.SettingsError(m)
690

    
691
        kwargs['configure_callback'] = configure_callback
692
        Setting.__init__(self, **kwargs)
693

    
694
    @staticmethod
695
    def configure_callback(setting, value, deps):
696
        raise NotImplementedError()
697

    
698

    
699
class Deprecated(object):
700
    """Deprecated settings must be removed, renamed, or otherwise fixed."""
701

    
702
    setting_type = 'deprecated'
703

    
704
    def __init__(self, rename_to=None, **kwargs):
705
        self.rename_to = rename_to
706
        kwargs['export'] = False
707
        Setting.__init__(self, **kwargs)
708

    
709
    @staticmethod
710
    def configure_callback(setting, value, deps):
711
        m = ("Setting {name} has been deprecated. "
712
             "Please consult upgrade notes and ")
713

    
714
        if setting.rename_to:
715
            m += "rename to {rename_to}."
716
        else:
717
            m += "remove it."
718

    
719
        m = m.format(name=setting.setting_name, rename_to=setting.rename_to)
720
        raise Setting.SettingsError(m)
721