Merge lp:~allenap/maas/argparse-the-annoying--bug-1557434 into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5173
Proposed branch: lp:~allenap/maas/argparse-the-annoying--bug-1557434
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 201 lines (+72/-17)
4 files modified
buildout.cfg (+21/-14)
src/maascli/__init__.py (+8/-1)
src/maascli/tests/test_cli.py (+1/-1)
src/maascli/tests/test_integration.py (+42/-1)
To merge this branch: bzr merge lp:~allenap/maas/argparse-the-annoying--bug-1557434
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+299936@code.launchpad.net

Commit message

For the MAAS CLI, mimic the error behaviour provided by argparse 1.1 on PyPI.

This differs substantially from the argparse 1.1 found in the standard library. This branch also forces the development environment to use argparse as found in the standard library instead of the one pulled in by unittest2.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Just tested this locally. The behavior is *much* better. Many thanks for this.

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

Thanks!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'buildout.cfg'
--- buildout.cfg 2016-07-07 18:52:38 +0000
+++ buildout.cfg 2016-07-13 11:35:29 +0000
@@ -45,15 +45,22 @@
45 testscenarios45 testscenarios
46 testtools46 testtools
47initialization =47initialization =
48 ${common:path-sort}48 ${common:path-munge}
49 ${common:warnings}49 ${common:warnings}
50 ${common:environment}50 ${common:environment}
51path-sort =51path-munge =
52 from pathlib import Path52 import pathlib, sys
53 from sys import path, prefix53 # Eliminate argparse usage outside of the standard library. This is
54 prefix = Path(prefix)54 # needed because some deps <cough>unittest2<cough> explicitly require
55 sortkey = lambda path: prefix in Path(path).parents55 # argparse, which zc.buildout then dutifully installs. Unfortunately
56 path.sort(key=sortkey)56 # argparse 1.1 from PyPI differs substantially to argparse 1.1 in the
57 # standard library. For consistency we want the latter.
58 p_argparse_egg = lambda path: pathlib.Path(path).match("*/argparse-*.egg")
59 sys.path[:] = [path for path in sys.path if not p_argparse_egg(path)]
60 # Sort system paths towards the end of sys.path so that deps defined
61 # here are used in preference to those installed system-wide.
62 p_sys_prefix = lambda path, p=pathlib.Path: p(sys.prefix) in p(path).parents
63 sys.path.sort(key=p_sys_prefix)
57environment =64environment =
58 from os import environ65 from os import environ
59 environ.setdefault("MAAS_ROOT", "${buildout:directory}/run")66 environ.setdefault("MAAS_ROOT", "${buildout:directory}/run")
@@ -80,7 +87,7 @@
80extra-paths =87extra-paths =
81 ${common:extra-paths}88 ${common:extra-paths}
82initialization =89initialization =
83 ${common:path-sort}90 ${common:path-munge}
84interpreter =91interpreter =
85entry-points = database=postgresfixture.main:main92entry-points = database=postgresfixture.main:main
86scripts = database93scripts = database
@@ -144,7 +151,7 @@
144eggs =151eggs =
145 ${region:eggs}152 ${region:eggs}
146initialization =153initialization =
147 ${common:path-sort}154 ${common:path-munge}
148entry-points =155entry-points =
149 maas=maascli:main156 maas=maascli:main
150extra-paths =157extra-paths =
@@ -160,7 +167,7 @@
160entry-points =167entry-points =
161 test.cli=maastesting.noseplug:main168 test.cli=maastesting.noseplug:main
162initialization =169initialization =
163 ${common:path-sort}170 ${common:path-munge}
164 ${common:warnings}171 ${common:warnings}
165 options = [172 options = [
166 "--with-resources",173 "--with-resources",
@@ -196,7 +203,7 @@
196entry-points =203entry-points =
197 test.testing=maastesting.noseplug:main204 test.testing=maastesting.noseplug:main
198initialization =205initialization =
199 ${common:path-sort}206 ${common:path-munge}
200 ${common:warnings}207 ${common:warnings}
201 options = [208 options = [
202 "--with-resources",209 "--with-resources",
@@ -283,7 +290,7 @@
283scripts =290scripts =
284 test.e2e291 test.e2e
285initialization =292initialization =
286 ${common:path-sort}293 ${common:path-munge}
287 from os import environ294 from os import environ
288 environ.setdefault("MAAS_RACK_DEVELOP", "TRUE")295 environ.setdefault("MAAS_RACK_DEVELOP", "TRUE")
289 environ.setdefault("MAAS_ROOT", "${buildout:directory}/run-e2e")296 environ.setdefault("MAAS_ROOT", "${buildout:directory}/run-e2e")
@@ -298,7 +305,7 @@
298entry-points =305entry-points =
299 flake8=flake8.run:main306 flake8=flake8.run:main
300initialization =307initialization =
301 ${common:path-sort}308 ${common:path-munge}
302 ${common:warnings}309 ${common:warnings}
303310
304[coverage]311[coverage]
@@ -308,7 +315,7 @@
308entry-points =315entry-points =
309 coverage=coverage.cmdline:main316 coverage=coverage.cmdline:main
310initialization =317initialization =
311 ${common:path-sort}318 ${common:path-munge}
312 ${common:warnings}319 ${common:warnings}
313scripts =320scripts =
314 coverage321 coverage
315322
=== modified file 'src/maascli/__init__.py'
--- src/maascli/__init__.py 2016-03-28 13:54:47 +0000
+++ src/maascli/__init__.py 2016-07-13 11:35:29 +0000
@@ -25,9 +25,16 @@
2525
26 try:26 try:
27 options = parser.parse_args(argv[1:])27 options = parser.parse_args(argv[1:])
28 options.execute(options)28 if hasattr(options, "execute"):
29 options.execute(options)
30 else:
31 # This mimics the error behaviour provided by argparse 1.1 from
32 # PyPI (which differs from argparse 1.1 in the standard library).
33 parser.error("too few arguments")
29 except KeyboardInterrupt:34 except KeyboardInterrupt:
30 raise SystemExit(1)35 raise SystemExit(1)
36 except SystemExit:
37 raise # Pass-through.
31 except Exception as error:38 except Exception as error:
32 if options.debug:39 if options.debug:
33 raise40 raise
3441
=== modified file 'src/maascli/tests/test_cli.py'
--- src/maascli/tests/test_cli.py 2016-06-22 17:03:02 +0000
+++ src/maascli/tests/test_cli.py 2016-07-13 11:35:29 +0000
@@ -26,7 +26,7 @@
26from testtools.matchers import DocTestMatches26from testtools.matchers import DocTestMatches
2727
2828
29class TestRegisterommands(MAASTestCase):29class TestRegisterCommands(MAASTestCase):
30 """Tests for registers CLI commands."""30 """Tests for registers CLI commands."""
3131
32 def test_registers_subparsers(self):32 def test_registers_subparsers(self):
3333
=== modified file 'src/maascli/tests/test_integration.py'
--- src/maascli/tests/test_integration.py 2015-12-01 18:12:59 +0000
+++ src/maascli/tests/test_integration.py 2016-07-13 11:35:29 +0000
@@ -1,4 +1,4 @@
1# Copyright 2012-2015 Canonical Ltd. This software is licensed under the1# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Integration-test the `maascli` command."""4"""Integration-test the `maascli` command."""
@@ -6,14 +6,23 @@
6__all__ = []6__all__ = []
77
8import os.path8import os.path
9import random
9from subprocess import (10from subprocess import (
10 CalledProcessError,11 CalledProcessError,
11 check_output,12 check_output,
12 STDOUT,13 STDOUT,
13)14)
15from textwrap import dedent
1416
17from maascli import main
18from maascli.config import ProfileConfig
19from maascli.testing.config import make_configs
20from maascli.utils import handler_command_name
15from maastesting import root21from maastesting import root
22from maastesting.fixtures import CaptureStandardIO
23from maastesting.matchers import DocTestMatches
16from maastesting.testcase import MAASTestCase24from maastesting.testcase import MAASTestCase
25from testtools.matchers import Equals
1726
1827
19def locate_maascli():28def locate_maascli():
@@ -54,3 +63,35 @@
54 else:63 else:
55 # The test is that we get here without error.64 # The test is that we get here without error.
56 pass65 pass
66
67
68class TestMain(MAASTestCase):
69 """Tests of `maascli.main` directly."""
70
71 def fake_profile(self):
72 """Fake a profile."""
73 configs = make_configs() # Instance of FakeConfig.
74 self.patch(ProfileConfig, 'open').return_value = configs
75 return configs
76
77 def test_complains_about_too_few_arguments(self):
78 configs = self.fake_profile()
79 [profile_name] = configs
80 resources = configs[profile_name]["description"]["resources"]
81 resource_name = random.choice(resources)["name"]
82 command = "maas", profile_name, handler_command_name(resource_name)
83
84 with CaptureStandardIO() as stdio:
85 error = self.assertRaises(SystemExit, main, command)
86
87 self.assertThat(error.code, Equals(2))
88 self.assertThat(
89 stdio.getError(),
90 DocTestMatches(dedent(
91 """\
92 usage: maas [-h] COMMAND ...
93 <BLANKLINE>
94 ...
95 <BLANKLINE>
96 too few arguments
97 """)))