From 4cf28c7e3bf437b859fe22bab08530ee7a32a598 Mon Sep 17 00:00:00 2001 From: Brian Stajkowski Date: Mon, 20 Jun 2016 07:58:31 -0700 Subject: [PATCH] Advanced Option Allows for an option to be tagged with advanced true/ false, notifying the end user about uncommon usage. Such an option is not normally used, and will be moved to the bottom of the respective namespace. Only options set to true (default: false) will be tagged as such in generated sample files. Change-Id: I2689aba283e473bb293bf0cf0c1c4bcb5979d115 Implements: blueprint advanced-option --- oslo_config/cfg.py | 49 ++++++++++++++++++++++++++++- oslo_config/generator.py | 9 +++++- oslo_config/sphinxext.py | 7 +++++ oslo_config/tests/test_generator.py | 46 +++++++++++++++++++++++++++ oslo_config/tests/test_sphinxext.py | 45 ++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 2 deletions(-) diff --git a/oslo_config/cfg.py b/oslo_config/cfg.py index d3ecd15..0a26b9d 100644 --- a/oslo_config/cfg.py +++ b/oslo_config/cfg.py @@ -357,6 +357,43 @@ command line arguments using the SubCommandOpt class: >>> conf.action.name, conf.action.id ('list', '10') +Advanced Option +--------------- + +Use if you need to label an option as advanced in sample files, indicating the +option is not normally used by the majority of users and might have a +significant effect on stability and/or performance:: + + from oslo_config import cfg + + opts = [ + cfg.StrOpt('option1', default='default_value', + advanced=True, help='This is help ' + 'text.'), + cfg.PortOpt('option2', default='default_value', + help='This is help text.'), + ] + + CONF = cfg.CONF + CONF.register_opts(opts) + +This will result in the option being pushed to the bottom of the +namespace and labeled as advanced in the sample files, with a notation +about possible effects:: + + [DEFAULT] + ... + # This is help text. (string value) + # option2 = default_value + ... + + ... + # This is help text. (string value) + # Advanced Option: intended for advanced users and not used + # by the majority of users, and might have a significant + # effect on stability and/or performance. + # option1 = default_value + """ import argparse @@ -667,6 +704,8 @@ class Opt(object): strings are encouraged. Silently ignored if deprecated_for_removal is False :param mutable: True if this option may be reloaded + :param advanced: a bool True/False value if this option has advanced usage + and is not normally used by the majority of users An Opt object has no public methods, but has a number of public properties: @@ -709,6 +748,10 @@ class Opt(object): a string explaining how the option's value is used + .. py:attribute:: advanced + + in sample files, a bool value indicating the option is advanced + .. versionchanged:: 1.2 Added *deprecated_opts* parameter. @@ -729,6 +772,9 @@ class Opt(object): .. versionchanged:: 3.12 Added *deprecated_since* parameter. + + .. versionchanged:: 3.15 + Added *advanced* parameter and attribute. """ multi = False @@ -738,7 +784,7 @@ class Opt(object): deprecated_name=None, deprecated_group=None, deprecated_opts=None, sample_default=None, deprecated_for_removal=False, deprecated_reason=None, - deprecated_since=None, mutable=False): + deprecated_since=None, mutable=False, advanced=False): if name.startswith('_'): raise ValueError('illegal name %s with prefix _' % (name,)) self.name = name @@ -776,6 +822,7 @@ class Opt(object): self._check_default() self.mutable = mutable + self.advanced = advanced def _default_is_ref(self): """Check if default is a reference to another var.""" diff --git a/oslo_config/generator.py b/oslo_config/generator.py index 550e0d9..22a9f28 100644 --- a/oslo_config/generator.py +++ b/oslo_config/generator.py @@ -259,6 +259,13 @@ class _OptFormatter(object): lines.extend( self._format_help('Reason: ' + opt.deprecated_reason)) + if opt.advanced: + lines.append( + '# Advanced Option: intended for advanced users and not used\n' + '# by the majority of users, and might have a significant\n' + '# effect on stability and/or performance.\n' + ) + if hasattr(opt.type, 'format_defaults'): defaults = opt.type.format_defaults(opt.default, opt.sample_default) @@ -389,7 +396,7 @@ def _output_opts(f, group, group_data, minimal=False): for (namespace, opts) in sorted(group_data['namespaces'], key=operator.itemgetter(0)): f.write('\n#\n# From %s\n#\n' % namespace) - for opt in opts: + for opt in sorted(opts, key=operator.attrgetter('advanced')): try: if minimal and not opt.required: pass diff --git a/oslo_config/sphinxext.py b/oslo_config/sphinxext.py index a7efb87..f1d082b 100644 --- a/oslo_config/sphinxext.py +++ b/oslo_config/sphinxext.py @@ -139,6 +139,13 @@ def _format_group(app, namespace, group_name, group_obj, opt_list): else: warnings.warn('Failed to fully format sample for %s: %s' % (opt.dest, err)) + if opt.advanced: + yield _indent( + ':Advanced Option: intended for advanced users and not used',) + yield _indent( + ':by the majority of users, and might have a significant',) + yield _indent( + ':effect on stability and/or performance.',) yield '' try: diff --git a/oslo_config/tests/test_generator.py b/oslo_config/tests/test_generator.py index b38df54..cdbca48 100644 --- a/oslo_config/tests/test_generator.py +++ b/oslo_config/tests/test_generator.py @@ -1219,4 +1219,50 @@ bars = self.assertEqual(expected, actual) +class AdvancedOptionsTestCase(base.BaseTestCase): + + opts = [cfg.StrOpt('foo', help='foo option', default='fred'), + cfg.StrOpt('bar', help='bar option', advanced=True), + cfg.StrOpt('foo_bar', help='foobar'), + cfg.BoolOpt('bars', help='bars foo', default=True, advanced=True)] + + def test_advanced_option_order_single_ns(self): + + config = [("namespace1", [ + ("alpha", self.opts)])] + groups = generator._get_groups(config) + + fd, tmp_file = tempfile.mkstemp() + with open(tmp_file, 'w+') as f: + formatter = generator._OptFormatter(output_file=f) + generator._output_opts(formatter, 'alpha', groups.pop('alpha')) + expected = '''[alpha] + +# +# From namespace1 +# + +# foo option (string value) +#foo = fred + +# foobar (string value) +#foo_bar = + +# bar option (string value) +# Advanced Option: intended for advanced users and not used +# by the majority of users, and might have a significant +# effect on stability and/or performance. +#bar = + +# bars foo (boolean value) +# Advanced Option: intended for advanced users and not used +# by the majority of users, and might have a significant +# effect on stability and/or performance. +#bars = true +''' + with open(tmp_file, 'r') as f: + actual = f.read() + self.assertEqual(expected, actual) + + GeneratorTestCase.generate_scenarios() diff --git a/oslo_config/tests/test_sphinxext.py b/oslo_config/tests/test_sphinxext.py index 8db5e77..03e7cbb 100644 --- a/oslo_config/tests/test_sphinxext.py +++ b/oslo_config/tests/test_sphinxext.py @@ -341,6 +341,51 @@ class FormatGroupTest(base.BaseTestCase): ''').lstrip(), results) + def test_advanced(self): + results = '\n'.join(list(sphinxext._format_group( + app=mock.Mock(), + namespace=None, + group_name=None, + group_obj=None, + opt_list=[ + cfg.StrOpt('opt_name', + advanced=True), + ], + ))) + self.assertEqual(textwrap.dedent(''' + .. oslo.config:group:: DEFAULT + + .. oslo.config:option:: opt_name + + :Type: string + :Default: ```` + :Advanced Option: intended for advanced users and not used + :by the majority of users, and might have a significant + :effect on stability and/or performance. + + ''').lstrip(), results) + + def test_not_advanced(self): + results = '\n'.join(list(sphinxext._format_group( + app=mock.Mock(), + namespace=None, + group_name=None, + group_obj=None, + opt_list=[ + cfg.StrOpt('opt_name', + advanced=False), + ], + ))) + self.assertEqual(textwrap.dedent(''' + .. oslo.config:group:: DEFAULT + + .. oslo.config:option:: opt_name + + :Type: string + :Default: ```` + + ''').lstrip(), results) + class FormatOptionHelpTest(base.BaseTestCase):