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
1=== added file 'src/mailman/commands/cli_mailmanconf.py'
2--- src/mailman/commands/cli_mailmanconf.py 1970-01-01 00:00:00 +0000
3+++ src/mailman/commands/cli_mailmanconf.py 2013-01-23 20:35:25 +0000
4@@ -0,0 +1,128 @@
5+# Copyright (C) 2009-2013 by the Free Software Foundation, Inc.
6+#
7+# This file is part of GNU Mailman.
8+#
9+# GNU Mailman is free software: you can redistribute it and/or modify it under
10+# the terms of the GNU General Public License as published by the Free
11+# Software Foundation, either version 3 of the License, or (at your option)
12+# any later version.
13+#
14+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
15+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17+# more details.
18+#
19+# You should have received a copy of the GNU General Public License along with
20+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
21+
22+"""Print the mailman configuration."""
23+
24+from __future__ import absolute_import, print_function, unicode_literals
25+
26+__metaclass__ = type
27+__all__ = [
28+ 'Mailmanconf'
29+ ]
30+
31+
32+import sys
33+
34+from zope.interface import implementer
35+from lazr.config._config import Section
36+
37+from mailman.config import config
38+from mailman.core.i18n import _
39+from mailman.interfaces.command import ICLISubCommand
40+
41+
42+
43
44+@implementer(ICLISubCommand)
45+class Mailmanconf:
46+ """Print the mailman configuration."""
47+
48+ name = 'mailmanconf'
49+
50+ def add(self, parser, command_parser):
51+ self.parser = parser
52+ """See `ICLISubCommand`."""
53+ command_parser.add_argument(
54+ '-o', '--output',
55+ action='store', help=_("""\
56+ File to send the output to. If not given, standard output is
57+ used."""))
58+ command_parser.add_argument(
59+ '-s', '--section',
60+ action='store', help=_("""\
61+ Section to use for the lookup. If no key is given,
62+ all the key-value pairs of the given section will be displayed.
63+ """))
64+ command_parser.add_argument(
65+ '-k', '--key',
66+ action='store', help=_("""\
67+ Key to use for the lookup. If no section is given,
68+ all the key-values pair from any section matching the given key
69+ will be displayed.
70+ """))
71+
72+ def _get_value(self, section, key):
73+ return getattr(getattr(config, section), key)
74+
75+ def _print_full_syntax(self, section, key, value, output):
76+ print('[{0}] {1}: {2}'.format(section, key, value), file=output)
77+
78+ def _show_key_error(self, section, key):
79+ self.parser.error('Section %s: No such key: %s' % (section, key))
80+
81+ def _show_section_error(self, section):
82+ self.parser.error('No such section: %s' % section)
83+
84+ def _print_values_for_section(self, section, output):
85+ current_section = getattr(config, section)
86+ for key in current_section:
87+ if hasattr(current_section, key):
88+ self._print_full_syntax(section, key, self._get_value(section, key), output)
89+
90+ def _section_exists(self, section):
91+ # not all the attributes in config are actual sections,
92+ # so we have to additionally check a sections type
93+ return hasattr(config, section) and isinstance(getattr(config, section), Section)
94+
95+ def process(self, args):
96+ """See `ICLISubCommand`."""
97+ if args.output is None:
98+ output = sys.stdout
99+ else:
100+ # We don't need to close output because that will happen
101+ # automatically when the script exits.
102+ output = open(args.output, 'w')
103+ section = args.section
104+ key = args.key
105+ # Case 1: Both section and key are given, we can directly look up the value
106+ if section is not None and key is not None:
107+ if not self._section_exists(section):
108+ self._show_section_error(section)
109+ elif not hasattr(getattr(config, section), key):
110+ self._show_key_error(section, key)
111+ else:
112+ print(self._get_value(section, key))
113+ # Case 2: Section is given, key is not given
114+ elif section is not None and key is None:
115+ if self._section_exists(section):
116+ self._print_values_for_section(section, output)
117+ else:
118+ self._show_section_error(section)
119+ # Case 3: Section is not given, key is given
120+ elif section is None and key is not None:
121+ for current_section in config.schema._section_schemas:
122+ # We have to ensure that the current section actually exists and
123+ # that it contains the given key
124+ if self._section_exists(current_section) and hasattr(getattr(config, current_section), key):
125+ self._print_full_syntax(current_section, key, self._get_value(current_section, key), output)
126+ # Case 4: Neither section nor key are given,
127+ # just display all the sections and their corresponding key/value pairs.
128+ elif section is None and key is None:
129+ for current_section in config.schema._section_schemas:
130+ # However, we have to make sure that the current sections and key
131+ # which are being looked up actually exist before trying to print them
132+ if self._section_exists(current_section):
133+ self._print_values_for_section(current_section, output)
134
135=== added file 'src/mailman/commands/docs/mailmanconf.rst'
136--- src/mailman/commands/docs/mailmanconf.rst 1970-01-01 00:00:00 +0000
137+++ src/mailman/commands/docs/mailmanconf.rst 2013-01-23 20:35:25 +0000
138@@ -0,0 +1,61 @@
139+==================
140+Display configuration values
141+==================
142+
143+Just like the postfix command postconf(1), mailmanconf lets you dump
144+one or more mailman configuration variables. Internally, these can be
145+retrieved by using the mailman.config.config object. Their structure
146+is based on the schema given by src/mailman/config/schema.cfg.
147+For more information on how the values are actually set, see
148+src/mailman/docs/START.rst
149+
150+Basically, the configuration is divided in multiple sections which
151+contain multiple key-value pairs. The ``bin/mailman mailmanconf``
152+command allows you to display a specific or several key-value pairs.
153+
154+ >>> class FakeArgs:
155+ ... key = None
156+ ... section = None
157+ ... output = None
158+ >>> from mailman.commands.cli_mailmanconf import Mailmanconf
159+ >>> command = Mailmanconf()
160+
161+To get a list of all key-value pairs of any section, you need to call
162+the command without any options.
163+
164+ >>> command.process(FakeArgs)
165+ ... [logging.archiver] path: mailman.log
166+ ... [logging.archiver] level: info
167+ ... [logging.locks] propagate: no
168+ ... [logging.locks] level: info
169+ ... [passwords] configuration: python:mailman.config.passlib
170+ ... etc.
171+
172+You can list all the key-value pairs of a specific section.
173+
174+ >>> FakeArgs.section = 'mailman'
175+ >>> command.process(FakeArgs)
176+ ... [mailman] filtered_messages_are_preservable: no
177+ ... [mailman] post_hook:
178+ ... [mailman] pending_request_life: 3d
179+ ... etc.
180+
181+You can also pass a key and display all key-value pairs matching
182+the given key, along with the names of the corresponding sections.
183+
184+ >>> FakeArgs.section = 'None'
185+ >>> FakeArgs.key = 'path'
186+ >>> command.process(FakeArgs)
187+ ... [logging.archiver] path: mailman.log
188+ ... [logging.mischief] path: mailman.log
189+ ... [logging.error] path: mailman.log
190+ ... [logging.smtp] path: smtp.log
191+ ... etc.
192+
193+If you specify both a section and a key, you will get the corresponding value.
194+
195+ >>> FakeArgs.section = 'mailman'
196+ >>> FakeArgs.key = 'site_owner'
197+ >>> command.process(FakeArgs)
198+ ... changeme@example.com
199+
200\ No newline at end of file
201
202=== added file 'src/mailman/commands/tests/test_mailmanconf.py'
203--- src/mailman/commands/tests/test_mailmanconf.py 1970-01-01 00:00:00 +0000
204+++ src/mailman/commands/tests/test_mailmanconf.py 2013-01-23 20:35:25 +0000
205@@ -0,0 +1,76 @@
206+# Copyright (C) 2011-2013 by the Free Software Foundation, Inc.
207+#
208+# This file is part of GNU Mailman.
209+#
210+# GNU Mailman is free software: you can redistribute it and/or modify it under
211+# the terms of the GNU General Public License as published by the Free
212+# Software Foundation, either version 3 of the License, or (at your option)
213+# any later version.
214+#
215+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
216+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
217+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
218+# more details.
219+#
220+# You should have received a copy of the GNU General Public License along with
221+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
222+
223+"""Test the mailmanconf subcommand."""
224+
225+from __future__ import absolute_import, unicode_literals
226+
227+__metaclass__ = type
228+__all__ = [
229+ ]
230+
231+import sys
232+import unittest
233+
234+from mailman.commands.cli_mailmanconf import Mailmanconf
235+from mailman.config import config
236+
237+
238
239+class FakeArgs:
240+ section = None
241+ key = None
242+ output = None
243+
244+
245+class FakeParser:
246+ def __init__(self):
247+ self.message = None
248+
249+ def error(self, message):
250+ self.message = message
251+ sys.exit(1)
252+
253+
254+
255
256+class TestMailmanconf(unittest.TestCase):
257+ """Test the mailmanconf subcommand."""
258+
259+ def setUp(self):
260+ self.command = Mailmanconf()
261+ self.command.parser = FakeParser()
262+ self.args = FakeArgs()
263+
264+ def test_cannot_access_nonexistent_section(self):
265+ self.args.section = 'thissectiondoesnotexist'
266+ self.args.key = None
267+ try:
268+ self.command.process(self.args)
269+ except SystemExit:
270+ pass
271+ self.assertEqual(self.command.parser.message,
272+ 'No such section: thissectiondoesnotexist')
273+
274+ def test_cannot_access_nonexistent_key(self):
275+ self.args.section = "mailman"
276+ self.args.key = 'thiskeydoesnotexist'
277+ try:
278+ self.command.process(self.args)
279+ except SystemExit:
280+ pass
281+ self.assertEqual(self.command.parser.message,
282+ 'Section mailman: No such key: thiskeydoesnotexist')
283+
284\ No newline at end of file