3 new commits in pytest:

https://bitbucket.org/hpk42/pytest/commits/a3f34a534d44/
Changeset:   a3f34a534d44
Branch:      argparse
User:        Anthon van der Neut
Date:        2013-07-25 15:33:43
Summary:     moving from optparse to argparse. Major difficulty is
that argparse does not have Option objects -> added class Argument
Needed explicit call of MyOptionParser.format_epilog as argparse
does not have that. The parse_arg epilog argument wraps the text,
which is not the same (could be handled with a special formatter).

- parser.parse() now returns single argument (with positional args in
  .file_or_dir)
- "file_or_dir" made a class variable Config._file_or_dir and used in help and 
tests
- added code for argcomplete (because of which this all started!)

addoption:
- if option type is a string ('int' or 'string', this converted to
  int resp. str
- if option type is 'count' this is changed to the type of choices[0]

testing:
- added tests for Argument
- test_mark.test_keyword_extra split as ['-k', '-mykeyword'] generates argparse
  error test split in two and one marked as fail
- testing hints, multiline and more strickt (for if someone moves format_epilog
  to epilog argument of parse_args without Formatter)
- test for destination derived from long option with internal dash
- renamed second test_parseopt.test_parse() to test_parse2 as it was
  not tested at all (the first was tested.)
Affected #:  11 files

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -6,7 +6,7 @@
 def pytest_addoption(parser):
     group = parser.getgroup("general")
     group._addoption('--capture', action="store", default=None,
-        metavar="method", type="choice", choices=['fd', 'sys', 'no'],
+        metavar="method", choices=['fd', 'sys', 'no'],
         help="per-test capturing method: one of fd (default)|sys|no.")
     group._addoption('-s', action="store_const", const="no", dest="capture",
         help="shortcut for --capture=no.")

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -80,16 +80,20 @@
         for group in groups:
             if group.options:
                 desc = group.description or group.name
-                optgroup = py.std.optparse.OptionGroup(optparser, desc)
-                optgroup.add_options(group.options)
-                optparser.add_option_group(optgroup)
+                arggroup = optparser.add_argument_group(desc)
+                for option in group.options:
+                    n = option.names()
+                    a = option.attrs()
+                    arggroup.add_argument(*n, **a)
+        optparser.add_argument(Config._file_or_dir, nargs='*')
+        try_argcomplete(self.optparser)
         return self.optparser.parse_args([str(x) for x in args])
 
     def parse_setoption(self, args, option):
-        parsedoption, args = self.parse(args)
+        parsedoption = self.parse(args)
         for name, value in parsedoption.__dict__.items():
             setattr(option, name, value)
-        return args
+        return getattr(parsedoption, Config._file_or_dir)
 
     def addini(self, name, help, type=None, default=None):
         """ register an ini-file option.
@@ -105,7 +109,133 @@
         self._inidict[name] = (help, type, default)
         self._ininames.append(name)
 
+def try_argcomplete(parser):
+    try:
+        import argcomplete
+    except ImportError:
+        pass
+    else:
+        argcomplete.autocomplete(parser)
 
+class ArgumentError(Exception):
+    """
+    Raised if an Argument instance is created with invalid or
+    inconsistent arguments.
+    """
+
+    def __init__(self, msg, option):
+        self.msg = msg
+        self.option_id = str(option)
+
+    def __str__(self):
+        if self.option_id:
+            return "option %s: %s" % (self.option_id, self.msg)
+        else:
+            return self.msg
+
+
+class Argument:
+    """class that mimics the necessary behaviour of py.std.optparse.Option """
+    _typ_map = {
+        'int': int,
+        'string': str,
+        }
+        
+    def __init__(self, *names, **attrs):
+        """store parms in private vars for use in add_argument"""
+        self._attrs = attrs
+        self._short_opts = []
+        self._long_opts = []
+        self.dest = attrs.get('dest')
+        try:
+            typ = attrs['type']
+        except KeyError:
+            pass
+        else:
+            # this might raise a keyerror as well, don't want to catch that
+            if isinstance(typ, str):
+                if typ == 'choice':
+                    # argparse expects a type here take it from
+                    # the type of the first element
+                    attrs['type'] = type(attrs['choices'][0])
+                else:
+                    attrs['type'] = Argument._typ_map[typ]
+                # used in test_parseopt -> test_parse_defaultgetter 
+                self.type = attrs['type']
+            else:
+                self.type = typ
+        try:
+            # attribute existence is tested in Config._processopt
+            self.default = attrs['default']
+        except KeyError:
+            pass
+        self._set_opt_strings(names)
+        if not self.dest:
+            if self._long_opts:
+                self.dest = self._long_opts[0][2:].replace('-', '_')
+            else:
+                try:
+                    self.dest = self._short_opts[0][1:]
+                except IndexError:
+                    raise ArgumentError(
+                        'need a long or short option', self)
+
+    def names(self):
+        return self._short_opts + self._long_opts
+
+    def attrs(self):
+        # update any attributes set by processopt
+        attrs = 'default dest'.split()
+        if self.dest:
+            attrs.append(self.dest)
+        for attr in attrs:
+            try:
+                self._attrs[attr] = getattr(self, attr)
+            except AttributeError:
+                pass
+        return self._attrs
+        
+    def _set_opt_strings(self, opts):
+        """directly from optparse
+
+        might not be necessary as this is passed to argparse later on"""
+        for opt in opts:
+            if len(opt) < 2:
+                raise ArgumentError(
+                    "invalid option string %r: "
+                    "must be at least two characters long" % opt, self)
+            elif len(opt) == 2:
+                if not (opt[0] == "-" and opt[1] != "-"):
+                    raise ArgumentError(
+                        "invalid short option string %r: "
+                        "must be of the form -x, (x any non-dash char)" % opt,
+                        self)
+                self._short_opts.append(opt)
+            else:
+                if not (opt[0:2] == "--" and opt[2] != "-"):
+                    raise ArgumentError(
+                        "invalid long option string %r: "
+                        "must start with --, followed by non-dash" % opt,
+                        self)
+                self._long_opts.append(opt)
+        
+    def __repr__(self):
+        retval = 'Argument('
+        if self._short_opts:
+            retval += '_short_opts: ' + repr(self._short_opts) + ', '
+        if self._long_opts:
+            retval += '_long_opts: ' + repr(self._long_opts) + ', '
+        retval += 'dest: ' + repr(self.dest) + ', '
+        if hasattr(self, 'type'):
+            retval += 'type: ' + repr(self.type) + ', '
+        if hasattr(self, 'default'):
+            retval += 'default: ' + repr(self.default) + ', '
+        if retval[-2:] == ', ':  # always long enough to test ("Argument(" )
+            retval = retval[:-2]
+        retval += ')'
+        return retval
+
+                        
 class OptionGroup:
     def __init__(self, name, description="", parser=None):
         self.name = name
@@ -115,11 +245,11 @@
 
     def addoption(self, *optnames, **attrs):
         """ add an option to this group. """
-        option = py.std.optparse.Option(*optnames, **attrs)
+        option = Argument(*optnames, **attrs)
         self._addoption_instance(option, shortupper=False)
 
     def _addoption(self, *optnames, **attrs):
-        option = py.std.optparse.Option(*optnames, **attrs)
+        option = Argument(*optnames, **attrs)
         self._addoption_instance(option, shortupper=True)
 
     def _addoption_instance(self, option, shortupper=False):
@@ -132,11 +262,11 @@
         self.options.append(option)
 
 
-class MyOptionParser(py.std.optparse.OptionParser):
+class MyOptionParser(py.std.argparse.ArgumentParser):
     def __init__(self, parser):
         self._parser = parser
-        py.std.optparse.OptionParser.__init__(self, usage=parser._usage,
-            add_help_option=False)
+        py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage,
+            add_help=False)
     def format_epilog(self, formatter):
         hints = self._parser.hints
         if hints:
@@ -263,12 +393,15 @@
 
 class Config(object):
     """ access to configuration values, pluginmanager and plugin hooks.  """
+    _file_or_dir = 'file_or_dir'
+    
     def __init__(self, pluginmanager=None):
         #: access to command line option as attributes.
         #: (deprecated), use :py:func:`getoption() 
<_pytest.config.Config.getoption>` instead
         self.option = CmdOptions()
+        _a = self._file_or_dir
         self._parser = Parser(
-            usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
+            usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
             processopt=self._processopt,
         )
         #: a pluginmanager instance

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -62,6 +62,7 @@
 def showhelp(config):
     tw = py.io.TerminalWriter()
     tw.write(config._parser.optparser.format_help())
+    tw.write(config._parser.optparser.format_epilog(None))
     tw.line()
     tw.line()
     #tw.sep( "=", "config file settings")

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -23,7 +23,7 @@
     """modify command line arguments before option parsing. """
 
 def pytest_addoption(parser):
-    """register optparse-style options and ini-style config values.
+    """register argparse-style options and ini-style config values.
 
     This function must be implemented in a :ref:`plugin <pluginorder>` and is
     called once at the beginning of a test run.

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -35,7 +35,7 @@
                dest="exitfirst",
                help="exit instantly on first error or failed test."),
     group._addoption('--maxfail', metavar="num",
-               action="store", type="int", dest="maxfail", default=0,
+               action="store", type=int, dest="maxfail", default=0,
                help="exit after first num failures or errors.")
     group._addoption('--strict', action="store_true",
                help="run pytest in strict mode, warnings become errors.")

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/pastebin.py
--- a/_pytest/pastebin.py
+++ b/_pytest/pastebin.py
@@ -10,7 +10,7 @@
     group = parser.getgroup("terminal reporting")
     group._addoption('--pastebin', metavar="mode",
         action='store', dest="pastebin", default=None,
-        type="choice", choices=['failed', 'all'],
+        choices=['failed', 'all'],
         help="send failed|all info to bpaste.net pastebin service.")
 
 def pytest_configure(__multicall__, config):

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/runner.py
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -18,7 +18,7 @@
 def pytest_addoption(parser):
     group = parser.getgroup("terminal reporting", "reporting", after="general")
     group.addoption('--durations',
-         action="store", type="int", default=None, metavar="N",
+         action="store", type=int, default=None, metavar="N",
          help="show N slowest setup/test durations (N=0 for all)."),
 
 def pytest_terminal_summary(terminalreporter):

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -25,7 +25,7 @@
          help="(deprecated, use -r)")
     group._addoption('--tb', metavar="style",
                action="store", dest="tbstyle", default='long',
-               type="choice", choices=['long', 'short', 'no', 'line', 
'native'],
+               choices=['long', 'short', 'no', 'line', 'native'],
                help="traceback print mode (long/short/line/native/no).")
     group._addoption('--fulltrace',
                action="store_true", dest="fulltrace", default=False,

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 pytest.py
--- a/pytest.py
+++ b/pytest.py
@@ -1,3 +1,4 @@
+# PYTHON_ARGCOMPLETE_OK
 """
 pytest: unit and functional testing with Python.
 """

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 testing/test_mark.py
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -451,12 +451,22 @@
                assert 0
            test_one.mykeyword = True
         """)
+        reprec = testdir.inline_run("-k", "mykeyword", p)
+        passed, skipped, failed = reprec.countoutcomes()
+        assert failed == 1
+
+    @pytest.mark.xfail
+    def test_keyword_extra_dash(self, testdir):
+        p = testdir.makepyfile("""
+           def test_one():
+               assert 0
+           test_one.mykeyword = True
+        """)
+        # with argparse the argument to an option cannot
+        # start with '-'
         reprec = testdir.inline_run("-k", "-mykeyword", p)
         passed, skipped, failed = reprec.countoutcomes()
         assert passed + skipped + failed == 0
-        reprec = testdir.inline_run("-k", "mykeyword", p)
-        passed, skipped, failed = reprec.countoutcomes()
-        assert failed == 1
 
     def test_no_magic_values(self, testdir):
         """Make sure the tests do not match on magic values,

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
a3f34a534d44feee1c77f5122402c761259a11d9 testing/test_parseopt.py
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -7,8 +7,44 @@
         parser = parseopt.Parser(usage="xyz")
         pytest.raises(SystemExit, 'parser.parse(["-h"])')
         out, err = capsys.readouterr()
-        assert err.find("no such option") != -1
+        assert err.find("error: unrecognized arguments") != -1
 
+    def test_argument(self):
+        with pytest.raises(parseopt.ArgumentError):
+            # need a short or long option
+            argument = parseopt.Argument()
+        argument = parseopt.Argument('-t')
+        assert argument._short_opts == ['-t']
+        assert argument._long_opts == []
+        assert argument.dest == 't'
+        argument = parseopt.Argument('-t', '--test')
+        assert argument._short_opts == ['-t']
+        assert argument._long_opts == ['--test']
+        assert argument.dest == 'test'
+        argument = parseopt.Argument('-t', '--test', dest='abc')
+        assert argument.dest == 'abc'
+
+    def test_argument_type(self):
+        argument = parseopt.Argument('-t', dest='abc', type='int')
+        assert argument.type is int
+        argument = parseopt.Argument('-t', dest='abc', type='string')
+        assert argument.type is str
+        argument = parseopt.Argument('-t', dest='abc', type=float)
+        assert argument.type is float
+        with pytest.raises(KeyError):
+            argument = parseopt.Argument('-t', dest='abc', type='choice')
+        argument = parseopt.Argument('-t', dest='abc', type='choice',
+                                     choices=['red', 'blue'])
+        assert argument.type is str
+
+    def test_argument_processopt(self):
+        argument = parseopt.Argument('-t', type=int)
+        argument.default = 42
+        argument.dest = 'abc'
+        res = argument.attrs()
+        assert res['default'] == 42
+        assert res['dest'] == 'abc'
+                    
     def test_group_add_and_get(self):
         parser = parseopt.Parser()
         group = parser.getgroup("hello", description="desc")
@@ -36,7 +72,7 @@
         group = parseopt.OptionGroup("hello")
         group.addoption("--option1", action="store_true")
         assert len(group.options) == 1
-        assert isinstance(group.options[0], py.std.optparse.Option)
+        assert isinstance(group.options[0], parseopt.Argument)
 
     def test_group_shortopt_lowercase(self):
         parser = parseopt.Parser()
@@ -58,19 +94,19 @@
     def test_parse(self):
         parser = parseopt.Parser()
         parser.addoption("--hello", dest="hello", action="store")
-        option, args = parser.parse(['--hello', 'world'])
-        assert option.hello == "world"
-        assert not args
+        args = parser.parse(['--hello', 'world'])
+        assert args.hello == "world"
+        assert not getattr(args, parseopt.Config._file_or_dir)
 
-    def test_parse(self):
+    def test_parse2(self):
         parser = parseopt.Parser()
-        option, args = parser.parse([py.path.local()])
-        assert args[0] == py.path.local()
+        args = parser.parse([py.path.local()])
+        assert getattr(args, parseopt.Config._file_or_dir)[0] == 
py.path.local()
 
     def test_parse_will_set_default(self):
         parser = parseopt.Parser()
         parser.addoption("--hello", dest="hello", default="x", action="store")
-        option, args = parser.parse([])
+        option = parser.parse([])
         assert option.hello == "x"
         del option.hello
         args = parser.parse_setoption([], option)
@@ -87,28 +123,37 @@
         assert option.world == 42
         assert not args
 
+    def test_parse_special_destination(self):
+        parser = parseopt.Parser()
+        x = parser.addoption("--ultimate-answer", type=int)
+        args = parser.parse(['--ultimate-answer', '42'])
+        assert args.ultimate_answer == 42
+        
     def test_parse_defaultgetter(self):
         def defaultget(option):
-            if option.type == "int":
+            if not hasattr(option, 'type'):
+                return
+            if option.type is int:
                 option.default = 42
-            elif option.type == "string":
+            elif option.type is str:
                 option.default = "world"
         parser = parseopt.Parser(processopt=defaultget)
         parser.addoption("--this", dest="this", type="int", action="store")
         parser.addoption("--hello", dest="hello", type="string", 
action="store")
         parser.addoption("--no", dest="no", action="store_true")
-        option, args = parser.parse([])
+        option = parser.parse([])
         assert option.hello == "world"
         assert option.this == 42
-
+        assert option.no is False
 
 @pytest.mark.skipif("sys.version_info < (2,5)")
 def test_addoption_parser_epilog(testdir):
     testdir.makeconftest("""
         def pytest_addoption(parser):
             parser.hints.append("hello world")
+            parser.hints.append("from me too")
     """)
     result = testdir.runpytest('--help')
     #assert result.ret != 0
-    result.stdout.fnmatch_lines(["*hint: hello world*"])
+    result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"])
 


https://bitbucket.org/hpk42/pytest/commits/8379d69dcb3f/
Changeset:   8379d69dcb3f
Branch:      argparse
User:        Anthon van der Neut
Date:        2013-07-25 17:26:48
Summary:     auto change %default -> %(default)s in help parameter string (on 
retrieval)
added code for warnings on optparse arguments (type, help),
which can be easily switched on with TYPE_WARN = True in config.py


installed and tested ( py.test --help )
pytest-quickcheck-0.7
pytest-gae-0.2.2
pytest-growl-0.1
pytest-bdd-0.4.7
pytest-bdd-splinter-0.4.4
pytest-cache-1.0
pytest-capturelog-0.7
pytest-codecheckers-0.2
pytest-contextfixture-0.1.1
pytest-cov-1.6
pytest-flakes-0.1
pytest-incremental-0.3.0
pytest-xdist-1.8
pytest-localserver-0.1.5
pytest-monkeyplus-1.1.0
pytest-oerp-0.2.0
pytest-pep8-1.0.4
pytest-pydev-0.1
pytest-rage-0.1
pytest-runfailed-0.3
pytest-timeout-0.3
pytest-xprocess-0.7
pytest-browsermob-proxy-0.1
pytest-mozwebqa-1.1.1
pytest-random-0.02
pytest-rerunfailures-0.03
pytest-zap-0.1
pytest-blockage-0.1
pytest-django-2.3.0
pytest-figleaf-1.0
pytest-greendots-0.1
pytest-instafail-0.1.0
pytest-konira-0.2
pytest-marker-bugzilla-0.06
pytest-marks-0.4
pytest-poo-0.2
pytest-twisted-1.4
pytest-yamlwsgi-0.6
Affected #:  1 file

diff -r a3f34a534d44feee1c77f5122402c761259a11d9 -r 
8379d69dcb3fbb3349daf1ae0a6d5441f8289a18 _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -5,6 +5,12 @@
 from _pytest.core import PluginManager
 import pytest
 
+# enable after some grace period for plugin writers
+TYPE_WARN = False
+if TYPE_WARN:
+    import warnings
+
+
 def pytest_cmdline_parse(pluginmanager, args):
     config = Config(pluginmanager)
     config.parse(args)
@@ -147,6 +153,17 @@
         self._short_opts = []
         self._long_opts = []
         self.dest = attrs.get('dest')
+        if TYPE_WARN:
+            try:
+                help = attrs['help']
+                if '%default' in help:
+                    warnings.warn(
+                        'py.test now uses argparse. "%default" should be'
+                        ' changed to "%(default)s" ',
+                        FutureWarning,
+                        stacklevel=3)
+            except KeyError:
+                pass
         try:
             typ = attrs['type']
         except KeyError:
@@ -155,10 +172,25 @@
             # this might raise a keyerror as well, don't want to catch that
             if isinstance(typ, str):
                 if typ == 'choice':
+                    if TYPE_WARN:
+                        warnings.warn(
+                            'type argument to addoption() is a string %r.'
+                            ' For parsearg this is optional and when supplied '
+                            ' should be a type.'
+                            ' (options: %s)' % (typ, names),
+                            FutureWarning,
+                            stacklevel=3)
                     # argparse expects a type here take it from
                     # the type of the first element
                     attrs['type'] = type(attrs['choices'][0])
                 else:
+                    if TYPE_WARN:
+                        warnings.warn(
+                            'type argument to addoption() is a string %r.'
+                            ' For parsearg this should be a type.'
+                            ' (options: %s)' % (typ, names),
+                            FutureWarning,
+                            stacklevel=3)
                     attrs['type'] = Argument._typ_map[typ]
                 # used in test_parseopt -> test_parse_defaultgetter 
                 self.type = attrs['type']
@@ -185,7 +217,7 @@
 
     def attrs(self):
         # update any attributes set by processopt
-        attrs = 'default dest'.split()
+        attrs = 'default dest help'.split()
         if self.dest:
             attrs.append(self.dest)
         for attr in attrs:
@@ -193,6 +225,11 @@
                 self._attrs[attr] = getattr(self, attr)
             except AttributeError:
                 pass
+        if self._attrs.get('help'):
+            a = self._attrs['help']
+            a = a.replace('%default', '%(default)s')
+            #a = a.replace('%prog', '%(prog)s')
+            self._attrs['help'] = a
         return self._attrs
         
     def _set_opt_strings(self, opts):


https://bitbucket.org/hpk42/pytest/commits/4405d5fd6cae/
Changeset:   4405d5fd6cae
User:        hpk42
Date:        2013-07-26 07:41:43
Summary:     Merged in anthon_van_der_neut/pytest/argparse (pull request #46)

argparse / argcomplete
Affected #:  11 files

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -6,7 +6,7 @@
 def pytest_addoption(parser):
     group = parser.getgroup("general")
     group._addoption('--capture', action="store", default=None,
-        metavar="method", type="choice", choices=['fd', 'sys', 'no'],
+        metavar="method", choices=['fd', 'sys', 'no'],
         help="per-test capturing method: one of fd (default)|sys|no.")
     group._addoption('-s', action="store_const", const="no", dest="capture",
         help="shortcut for --capture=no.")

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -5,6 +5,12 @@
 from _pytest.core import PluginManager
 import pytest
 
+# enable after some grace period for plugin writers
+TYPE_WARN = False
+if TYPE_WARN:
+    import warnings
+
+
 def pytest_cmdline_parse(pluginmanager, args):
     config = Config(pluginmanager)
     config.parse(args)
@@ -80,16 +86,20 @@
         for group in groups:
             if group.options:
                 desc = group.description or group.name
-                optgroup = py.std.optparse.OptionGroup(optparser, desc)
-                optgroup.add_options(group.options)
-                optparser.add_option_group(optgroup)
+                arggroup = optparser.add_argument_group(desc)
+                for option in group.options:
+                    n = option.names()
+                    a = option.attrs()
+                    arggroup.add_argument(*n, **a)
+        optparser.add_argument(Config._file_or_dir, nargs='*')
+        try_argcomplete(self.optparser)
         return self.optparser.parse_args([str(x) for x in args])
 
     def parse_setoption(self, args, option):
-        parsedoption, args = self.parse(args)
+        parsedoption = self.parse(args)
         for name, value in parsedoption.__dict__.items():
             setattr(option, name, value)
-        return args
+        return getattr(parsedoption, Config._file_or_dir)
 
     def addini(self, name, help, type=None, default=None):
         """ register an ini-file option.
@@ -105,7 +115,164 @@
         self._inidict[name] = (help, type, default)
         self._ininames.append(name)
 
+def try_argcomplete(parser):
+    try:
+        import argcomplete
+    except ImportError:
+        pass
+    else:
+        argcomplete.autocomplete(parser)
 
+class ArgumentError(Exception):
+    """
+    Raised if an Argument instance is created with invalid or
+    inconsistent arguments.
+    """
+
+    def __init__(self, msg, option):
+        self.msg = msg
+        self.option_id = str(option)
+
+    def __str__(self):
+        if self.option_id:
+            return "option %s: %s" % (self.option_id, self.msg)
+        else:
+            return self.msg
+
+
+class Argument:
+    """class that mimics the necessary behaviour of py.std.optparse.Option """
+    _typ_map = {
+        'int': int,
+        'string': str,
+        }
+        
+    def __init__(self, *names, **attrs):
+        """store parms in private vars for use in add_argument"""
+        self._attrs = attrs
+        self._short_opts = []
+        self._long_opts = []
+        self.dest = attrs.get('dest')
+        if TYPE_WARN:
+            try:
+                help = attrs['help']
+                if '%default' in help:
+                    warnings.warn(
+                        'py.test now uses argparse. "%default" should be'
+                        ' changed to "%(default)s" ',
+                        FutureWarning,
+                        stacklevel=3)
+            except KeyError:
+                pass
+        try:
+            typ = attrs['type']
+        except KeyError:
+            pass
+        else:
+            # this might raise a keyerror as well, don't want to catch that
+            if isinstance(typ, str):
+                if typ == 'choice':
+                    if TYPE_WARN:
+                        warnings.warn(
+                            'type argument to addoption() is a string %r.'
+                            ' For parsearg this is optional and when supplied '
+                            ' should be a type.'
+                            ' (options: %s)' % (typ, names),
+                            FutureWarning,
+                            stacklevel=3)
+                    # argparse expects a type here take it from
+                    # the type of the first element
+                    attrs['type'] = type(attrs['choices'][0])
+                else:
+                    if TYPE_WARN:
+                        warnings.warn(
+                            'type argument to addoption() is a string %r.'
+                            ' For parsearg this should be a type.'
+                            ' (options: %s)' % (typ, names),
+                            FutureWarning,
+                            stacklevel=3)
+                    attrs['type'] = Argument._typ_map[typ]
+                # used in test_parseopt -> test_parse_defaultgetter 
+                self.type = attrs['type']
+            else:
+                self.type = typ
+        try:
+            # attribute existence is tested in Config._processopt
+            self.default = attrs['default']
+        except KeyError:
+            pass
+        self._set_opt_strings(names)
+        if not self.dest:
+            if self._long_opts:
+                self.dest = self._long_opts[0][2:].replace('-', '_')
+            else:
+                try:
+                    self.dest = self._short_opts[0][1:]
+                except IndexError:
+                    raise ArgumentError(
+                        'need a long or short option', self)
+
+    def names(self):
+        return self._short_opts + self._long_opts
+
+    def attrs(self):
+        # update any attributes set by processopt
+        attrs = 'default dest help'.split()
+        if self.dest:
+            attrs.append(self.dest)
+        for attr in attrs:
+            try:
+                self._attrs[attr] = getattr(self, attr)
+            except AttributeError:
+                pass
+        if self._attrs.get('help'):
+            a = self._attrs['help']
+            a = a.replace('%default', '%(default)s')
+            #a = a.replace('%prog', '%(prog)s')
+            self._attrs['help'] = a
+        return self._attrs
+        
+    def _set_opt_strings(self, opts):
+        """directly from optparse
+
+        might not be necessary as this is passed to argparse later on"""
+        for opt in opts:
+            if len(opt) < 2:
+                raise ArgumentError(
+                    "invalid option string %r: "
+                    "must be at least two characters long" % opt, self)
+            elif len(opt) == 2:
+                if not (opt[0] == "-" and opt[1] != "-"):
+                    raise ArgumentError(
+                        "invalid short option string %r: "
+                        "must be of the form -x, (x any non-dash char)" % opt,
+                        self)
+                self._short_opts.append(opt)
+            else:
+                if not (opt[0:2] == "--" and opt[2] != "-"):
+                    raise ArgumentError(
+                        "invalid long option string %r: "
+                        "must start with --, followed by non-dash" % opt,
+                        self)
+                self._long_opts.append(opt)
+        
+    def __repr__(self):
+        retval = 'Argument('
+        if self._short_opts:
+            retval += '_short_opts: ' + repr(self._short_opts) + ', '
+        if self._long_opts:
+            retval += '_long_opts: ' + repr(self._long_opts) + ', '
+        retval += 'dest: ' + repr(self.dest) + ', '
+        if hasattr(self, 'type'):
+            retval += 'type: ' + repr(self.type) + ', '
+        if hasattr(self, 'default'):
+            retval += 'default: ' + repr(self.default) + ', '
+        if retval[-2:] == ', ':  # always long enough to test ("Argument(" )
+            retval = retval[:-2]
+        retval += ')'
+        return retval
+
+                        
 class OptionGroup:
     def __init__(self, name, description="", parser=None):
         self.name = name
@@ -115,11 +282,11 @@
 
     def addoption(self, *optnames, **attrs):
         """ add an option to this group. """
-        option = py.std.optparse.Option(*optnames, **attrs)
+        option = Argument(*optnames, **attrs)
         self._addoption_instance(option, shortupper=False)
 
     def _addoption(self, *optnames, **attrs):
-        option = py.std.optparse.Option(*optnames, **attrs)
+        option = Argument(*optnames, **attrs)
         self._addoption_instance(option, shortupper=True)
 
     def _addoption_instance(self, option, shortupper=False):
@@ -132,11 +299,11 @@
         self.options.append(option)
 
 
-class MyOptionParser(py.std.optparse.OptionParser):
+class MyOptionParser(py.std.argparse.ArgumentParser):
     def __init__(self, parser):
         self._parser = parser
-        py.std.optparse.OptionParser.__init__(self, usage=parser._usage,
-            add_help_option=False)
+        py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage,
+            add_help=False)
     def format_epilog(self, formatter):
         hints = self._parser.hints
         if hints:
@@ -263,12 +430,15 @@
 
 class Config(object):
     """ access to configuration values, pluginmanager and plugin hooks.  """
+    _file_or_dir = 'file_or_dir'
+    
     def __init__(self, pluginmanager=None):
         #: access to command line option as attributes.
         #: (deprecated), use :py:func:`getoption() 
<_pytest.config.Config.getoption>` instead
         self.option = CmdOptions()
+        _a = self._file_or_dir
         self._parser = Parser(
-            usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
+            usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
             processopt=self._processopt,
         )
         #: a pluginmanager instance

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -62,6 +62,7 @@
 def showhelp(config):
     tw = py.io.TerminalWriter()
     tw.write(config._parser.optparser.format_help())
+    tw.write(config._parser.optparser.format_epilog(None))
     tw.line()
     tw.line()
     #tw.sep( "=", "config file settings")

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -23,7 +23,7 @@
     """modify command line arguments before option parsing. """
 
 def pytest_addoption(parser):
-    """register optparse-style options and ini-style config values.
+    """register argparse-style options and ini-style config values.
 
     This function must be implemented in a :ref:`plugin <pluginorder>` and is
     called once at the beginning of a test run.

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -35,7 +35,7 @@
                dest="exitfirst",
                help="exit instantly on first error or failed test."),
     group._addoption('--maxfail', metavar="num",
-               action="store", type="int", dest="maxfail", default=0,
+               action="store", type=int, dest="maxfail", default=0,
                help="exit after first num failures or errors.")
     group._addoption('--strict', action="store_true",
                help="run pytest in strict mode, warnings become errors.")

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/pastebin.py
--- a/_pytest/pastebin.py
+++ b/_pytest/pastebin.py
@@ -10,7 +10,7 @@
     group = parser.getgroup("terminal reporting")
     group._addoption('--pastebin', metavar="mode",
         action='store', dest="pastebin", default=None,
-        type="choice", choices=['failed', 'all'],
+        choices=['failed', 'all'],
         help="send failed|all info to bpaste.net pastebin service.")
 
 def pytest_configure(__multicall__, config):

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/runner.py
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -18,7 +18,7 @@
 def pytest_addoption(parser):
     group = parser.getgroup("terminal reporting", "reporting", after="general")
     group.addoption('--durations',
-         action="store", type="int", default=None, metavar="N",
+         action="store", type=int, default=None, metavar="N",
          help="show N slowest setup/test durations (N=0 for all)."),
 
 def pytest_terminal_summary(terminalreporter):

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/terminal.py
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -25,7 +25,7 @@
          help="(deprecated, use -r)")
     group._addoption('--tb', metavar="style",
                action="store", dest="tbstyle", default='long',
-               type="choice", choices=['long', 'short', 'no', 'line', 
'native'],
+               choices=['long', 'short', 'no', 'line', 'native'],
                help="traceback print mode (long/short/line/native/no).")
     group._addoption('--fulltrace',
                action="store_true", dest="fulltrace", default=False,

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec pytest.py
--- a/pytest.py
+++ b/pytest.py
@@ -1,3 +1,4 @@
+# PYTHON_ARGCOMPLETE_OK
 """
 pytest: unit and functional testing with Python.
 """

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec testing/test_mark.py
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -451,12 +451,22 @@
                assert 0
            test_one.mykeyword = True
         """)
+        reprec = testdir.inline_run("-k", "mykeyword", p)
+        passed, skipped, failed = reprec.countoutcomes()
+        assert failed == 1
+
+    @pytest.mark.xfail
+    def test_keyword_extra_dash(self, testdir):
+        p = testdir.makepyfile("""
+           def test_one():
+               assert 0
+           test_one.mykeyword = True
+        """)
+        # with argparse the argument to an option cannot
+        # start with '-'
         reprec = testdir.inline_run("-k", "-mykeyword", p)
         passed, skipped, failed = reprec.countoutcomes()
         assert passed + skipped + failed == 0
-        reprec = testdir.inline_run("-k", "mykeyword", p)
-        passed, skipped, failed = reprec.countoutcomes()
-        assert failed == 1
 
     def test_no_magic_values(self, testdir):
         """Make sure the tests do not match on magic values,

diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 
4405d5fd6caec4072177911d9af4e1c57fe66cec testing/test_parseopt.py
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -7,8 +7,44 @@
         parser = parseopt.Parser(usage="xyz")
         pytest.raises(SystemExit, 'parser.parse(["-h"])')
         out, err = capsys.readouterr()
-        assert err.find("no such option") != -1
+        assert err.find("error: unrecognized arguments") != -1
 
+    def test_argument(self):
+        with pytest.raises(parseopt.ArgumentError):
+            # need a short or long option
+            argument = parseopt.Argument()
+        argument = parseopt.Argument('-t')
+        assert argument._short_opts == ['-t']
+        assert argument._long_opts == []
+        assert argument.dest == 't'
+        argument = parseopt.Argument('-t', '--test')
+        assert argument._short_opts == ['-t']
+        assert argument._long_opts == ['--test']
+        assert argument.dest == 'test'
+        argument = parseopt.Argument('-t', '--test', dest='abc')
+        assert argument.dest == 'abc'
+
+    def test_argument_type(self):
+        argument = parseopt.Argument('-t', dest='abc', type='int')
+        assert argument.type is int
+        argument = parseopt.Argument('-t', dest='abc', type='string')
+        assert argument.type is str
+        argument = parseopt.Argument('-t', dest='abc', type=float)
+        assert argument.type is float
+        with pytest.raises(KeyError):
+            argument = parseopt.Argument('-t', dest='abc', type='choice')
+        argument = parseopt.Argument('-t', dest='abc', type='choice',
+                                     choices=['red', 'blue'])
+        assert argument.type is str
+
+    def test_argument_processopt(self):
+        argument = parseopt.Argument('-t', type=int)
+        argument.default = 42
+        argument.dest = 'abc'
+        res = argument.attrs()
+        assert res['default'] == 42
+        assert res['dest'] == 'abc'
+                    
     def test_group_add_and_get(self):
         parser = parseopt.Parser()
         group = parser.getgroup("hello", description="desc")
@@ -36,7 +72,7 @@
         group = parseopt.OptionGroup("hello")
         group.addoption("--option1", action="store_true")
         assert len(group.options) == 1
-        assert isinstance(group.options[0], py.std.optparse.Option)
+        assert isinstance(group.options[0], parseopt.Argument)
 
     def test_group_shortopt_lowercase(self):
         parser = parseopt.Parser()
@@ -58,19 +94,19 @@
     def test_parse(self):
         parser = parseopt.Parser()
         parser.addoption("--hello", dest="hello", action="store")
-        option, args = parser.parse(['--hello', 'world'])
-        assert option.hello == "world"
-        assert not args
+        args = parser.parse(['--hello', 'world'])
+        assert args.hello == "world"
+        assert not getattr(args, parseopt.Config._file_or_dir)
 
-    def test_parse(self):
+    def test_parse2(self):
         parser = parseopt.Parser()
-        option, args = parser.parse([py.path.local()])
-        assert args[0] == py.path.local()
+        args = parser.parse([py.path.local()])
+        assert getattr(args, parseopt.Config._file_or_dir)[0] == 
py.path.local()
 
     def test_parse_will_set_default(self):
         parser = parseopt.Parser()
         parser.addoption("--hello", dest="hello", default="x", action="store")
-        option, args = parser.parse([])
+        option = parser.parse([])
         assert option.hello == "x"
         del option.hello
         args = parser.parse_setoption([], option)
@@ -87,28 +123,37 @@
         assert option.world == 42
         assert not args
 
+    def test_parse_special_destination(self):
+        parser = parseopt.Parser()
+        x = parser.addoption("--ultimate-answer", type=int)
+        args = parser.parse(['--ultimate-answer', '42'])
+        assert args.ultimate_answer == 42
+        
     def test_parse_defaultgetter(self):
         def defaultget(option):
-            if option.type == "int":
+            if not hasattr(option, 'type'):
+                return
+            if option.type is int:
                 option.default = 42
-            elif option.type == "string":
+            elif option.type is str:
                 option.default = "world"
         parser = parseopt.Parser(processopt=defaultget)
         parser.addoption("--this", dest="this", type="int", action="store")
         parser.addoption("--hello", dest="hello", type="string", 
action="store")
         parser.addoption("--no", dest="no", action="store_true")
-        option, args = parser.parse([])
+        option = parser.parse([])
         assert option.hello == "world"
         assert option.this == 42
-
+        assert option.no is False
 
 @pytest.mark.skipif("sys.version_info < (2,5)")
 def test_addoption_parser_epilog(testdir):
     testdir.makeconftest("""
         def pytest_addoption(parser):
             parser.hints.append("hello world")
+            parser.hints.append("from me too")
     """)
     result = testdir.runpytest('--help')
     #assert result.ret != 0
-    result.stdout.fnmatch_lines(["*hint: hello world*"])
+    result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"])

Repository URL: https://bitbucket.org/hpk42/pytest/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
_______________________________________________
pytest-commit mailing list
pytest-commit@python.org
http://mail.python.org/mailman/listinfo/pytest-commit

Reply via email to