Merge lp:~david-soto/mailman/postconf_equivalent into lp:mailman

Proposed by David Soto
Status: Merged
Approved by: Barry Warsaw
Approved revision: 7211
Merged at revision: 7206
Proposed branch: lp:~david-soto/mailman/postconf_equivalent
Merge into: lp:mailman
Diff against target: 281 lines (+265/-0)
3 files modified
src/mailman/commands/cli_mailmanconf.py (+128/-0)
src/mailman/commands/docs/mailmanconf.rst (+61/-0)
src/mailman/commands/tests/test_mailmanconf.py (+76/-0)
To merge this branch: bzr merge lp:~david-soto/mailman/postconf_equivalent
Reviewer Review Type Date Requested Status
Barry Warsaw Approve
Review via email: mp+144565@code.launchpad.net

Description of the change

This branch adds a postconf equivalent to the subcommands which displays configuration values as described in #518517. I have included a test class with two methods which ensure that the command displays an error message if invalid sections or keys are passed.
You can call the command by executing "/bin/mailman mailmanconf -s section -k key".

To post a comment you must log in.
7211. By David Soto

documentation added

Revision history for this message
Barry Warsaw (barry) wrote :

This branch looks great, and I'm really sorry for letting this sit for so long. I'm finally merging this at Pycon 2013, and thanks for your contribution to Mailman!

I made a few changes. I renamed it from Mailmanconf to Conf and I renamed the command from 'mailmanconf' to just 'conf' (the 'mailman' prefix part seemed redundant since this always gets run as a subcommand). I also added a few unittests for making sure that output to a file works, and I added "-o -" as a synonym for output to stdout. Also, TestConf must have a "layer = ConfigLayer" attribute otherwise the configuration system isn't set up by the time the tests run. Ironically this doesn't affect the failing tests, but with the expected-to-succeed tests I added, this has to be done.

I just cleaned up a few other things, but basically kept your code pretty well intact. Great work.

BTW, what do you think of adding a --sort/-s option so that the output is printed in sorted order, i.e. first by section and then by key?

If you like the idea, how would you like doing a new branch to add that feature?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'src/mailman/commands/cli_mailmanconf.py'
--- src/mailman/commands/cli_mailmanconf.py 1970-01-01 00:00:00 +0000
+++ src/mailman/commands/cli_mailmanconf.py 2013-01-23 20:35:25 +0000
@@ -0,0 +1,128 @@
1# Copyright (C) 2009-2013 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
5# GNU Mailman is free software: you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free
7# Software Foundation, either version 3 of the License, or (at your option)
8# any later version.
9#
10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
17
18"""Print the mailman configuration."""
19
20from __future__ import absolute_import, print_function, unicode_literals
21
22__metaclass__ = type
23__all__ = [
24 'Mailmanconf'
25 ]
26
27
28import sys
29
30from zope.interface import implementer
31from lazr.config._config import Section
32
33from mailman.config import config
34from mailman.core.i18n import _
35from mailman.interfaces.command import ICLISubCommand
36
37
38
039
40@implementer(ICLISubCommand)
41class Mailmanconf:
42 """Print the mailman configuration."""
43
44 name = 'mailmanconf'
45
46 def add(self, parser, command_parser):
47 self.parser = parser
48 """See `ICLISubCommand`."""
49 command_parser.add_argument(
50 '-o', '--output',
51 action='store', help=_("""\
52 File to send the output to. If not given, standard output is
53 used."""))
54 command_parser.add_argument(
55 '-s', '--section',
56 action='store', help=_("""\
57 Section to use for the lookup. If no key is given,
58 all the key-value pairs of the given section will be displayed.
59 """))
60 command_parser.add_argument(
61 '-k', '--key',
62 action='store', help=_("""\
63 Key to use for the lookup. If no section is given,
64 all the key-values pair from any section matching the given key
65 will be displayed.
66 """))
67
68 def _get_value(self, section, key):
69 return getattr(getattr(config, section), key)
70
71 def _print_full_syntax(self, section, key, value, output):
72 print('[{0}] {1}: {2}'.format(section, key, value), file=output)
73
74 def _show_key_error(self, section, key):
75 self.parser.error('Section %s: No such key: %s' % (section, key))
76
77 def _show_section_error(self, section):
78 self.parser.error('No such section: %s' % section)
79
80 def _print_values_for_section(self, section, output):
81 current_section = getattr(config, section)
82 for key in current_section:
83 if hasattr(current_section, key):
84 self._print_full_syntax(section, key, self._get_value(section, key), output)
85
86 def _section_exists(self, section):
87 # not all the attributes in config are actual sections,
88 # so we have to additionally check a sections type
89 return hasattr(config, section) and isinstance(getattr(config, section), Section)
90
91 def process(self, args):
92 """See `ICLISubCommand`."""
93 if args.output is None:
94 output = sys.stdout
95 else:
96 # We don't need to close output because that will happen
97 # automatically when the script exits.
98 output = open(args.output, 'w')
99 section = args.section
100 key = args.key
101 # Case 1: Both section and key are given, we can directly look up the value
102 if section is not None and key is not None:
103 if not self._section_exists(section):
104 self._show_section_error(section)
105 elif not hasattr(getattr(config, section), key):
106 self._show_key_error(section, key)
107 else:
108 print(self._get_value(section, key))
109 # Case 2: Section is given, key is not given
110 elif section is not None and key is None:
111 if self._section_exists(section):
112 self._print_values_for_section(section, output)
113 else:
114 self._show_section_error(section)
115 # Case 3: Section is not given, key is given
116 elif section is None and key is not None:
117 for current_section in config.schema._section_schemas:
118 # We have to ensure that the current section actually exists and
119 # that it contains the given key
120 if self._section_exists(current_section) and hasattr(getattr(config, current_section), key):
121 self._print_full_syntax(current_section, key, self._get_value(current_section, key), output)
122 # Case 4: Neither section nor key are given,
123 # just display all the sections and their corresponding key/value pairs.
124 elif section is None and key is None:
125 for current_section in config.schema._section_schemas:
126 # However, we have to make sure that the current sections and key
127 # which are being looked up actually exist before trying to print them
128 if self._section_exists(current_section):
129 self._print_values_for_section(current_section, output)
1130
=== added file 'src/mailman/commands/docs/mailmanconf.rst'
--- src/mailman/commands/docs/mailmanconf.rst 1970-01-01 00:00:00 +0000
+++ src/mailman/commands/docs/mailmanconf.rst 2013-01-23 20:35:25 +0000
@@ -0,0 +1,61 @@
1==================
2Display configuration values
3==================
4
5Just like the postfix command postconf(1), mailmanconf lets you dump
6one or more mailman configuration variables. Internally, these can be
7retrieved by using the mailman.config.config object. Their structure
8is based on the schema given by src/mailman/config/schema.cfg.
9For more information on how the values are actually set, see
10src/mailman/docs/START.rst
11
12Basically, the configuration is divided in multiple sections which
13contain multiple key-value pairs. The ``bin/mailman mailmanconf``
14command allows you to display a specific or several key-value pairs.
15
16 >>> class FakeArgs:
17 ... key = None
18 ... section = None
19 ... output = None
20 >>> from mailman.commands.cli_mailmanconf import Mailmanconf
21 >>> command = Mailmanconf()
22
23To get a list of all key-value pairs of any section, you need to call
24the command without any options.
25
26 >>> command.process(FakeArgs)
27 ... [logging.archiver] path: mailman.log
28 ... [logging.archiver] level: info
29 ... [logging.locks] propagate: no
30 ... [logging.locks] level: info
31 ... [passwords] configuration: python:mailman.config.passlib
32 ... etc.
33
34You can list all the key-value pairs of a specific section.
35
36 >>> FakeArgs.section = 'mailman'
37 >>> command.process(FakeArgs)
38 ... [mailman] filtered_messages_are_preservable: no
39 ... [mailman] post_hook:
40 ... [mailman] pending_request_life: 3d
41 ... etc.
42
43You can also pass a key and display all key-value pairs matching
44the given key, along with the names of the corresponding sections.
45
46 >>> FakeArgs.section = 'None'
47 >>> FakeArgs.key = 'path'
48 >>> command.process(FakeArgs)
49 ... [logging.archiver] path: mailman.log
50 ... [logging.mischief] path: mailman.log
51 ... [logging.error] path: mailman.log
52 ... [logging.smtp] path: smtp.log
53 ... etc.
54
55If you specify both a section and a key, you will get the corresponding value.
56
57 >>> FakeArgs.section = 'mailman'
58 >>> FakeArgs.key = 'site_owner'
59 >>> command.process(FakeArgs)
60 ... changeme@example.com
61
0\ No newline at end of file62\ No newline at end of file
163
=== added file 'src/mailman/commands/tests/test_mailmanconf.py'
--- src/mailman/commands/tests/test_mailmanconf.py 1970-01-01 00:00:00 +0000
+++ src/mailman/commands/tests/test_mailmanconf.py 2013-01-23 20:35:25 +0000
@@ -0,0 +1,76 @@
1# Copyright (C) 2011-2013 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
5# GNU Mailman is free software: you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free
7# Software Foundation, either version 3 of the License, or (at your option)
8# any later version.
9#
10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
17
18"""Test the mailmanconf subcommand."""
19
20from __future__ import absolute_import, unicode_literals
21
22__metaclass__ = type
23__all__ = [
24 ]
25
26import sys
27import unittest
28
29from mailman.commands.cli_mailmanconf import Mailmanconf
30from mailman.config import config
31
32
033
34class FakeArgs:
35 section = None
36 key = None
37 output = None
38
39
40class FakeParser:
41 def __init__(self):
42 self.message = None
43
44 def error(self, message):
45 self.message = message
46 sys.exit(1)
47
48
49
150
51class TestMailmanconf(unittest.TestCase):
52 """Test the mailmanconf subcommand."""
53
54 def setUp(self):
55 self.command = Mailmanconf()
56 self.command.parser = FakeParser()
57 self.args = FakeArgs()
58
59 def test_cannot_access_nonexistent_section(self):
60 self.args.section = 'thissectiondoesnotexist'
61 self.args.key = None
62 try:
63 self.command.process(self.args)
64 except SystemExit:
65 pass
66 self.assertEqual(self.command.parser.message,
67 'No such section: thissectiondoesnotexist')
68
69 def test_cannot_access_nonexistent_key(self):
70 self.args.section = "mailman"
71 self.args.key = 'thiskeydoesnotexist'
72 try:
73 self.command.process(self.args)
74 except SystemExit:
75 pass
76 self.assertEqual(self.command.parser.message,
77 'Section mailman: No such key: thiskeydoesnotexist')
78
2\ No newline at end of file79\ No newline at end of file