]> granicus.if.org Git - python/commitdiff
Merge #12776,#11839: call argparse type function only once.
authorR David Murray <rdmurray@bitdance.com>
Sat, 1 Sep 2012 03:09:34 +0000 (23:09 -0400)
committerR David Murray <rdmurray@bitdance.com>
Sat, 1 Sep 2012 03:09:34 +0000 (23:09 -0400)
Before, the type function was called twice in the case where the default
was specified and the argument was given as well.  This was especially
problematic for the FileType type, as a default file would always be
opened, even if a file argument was specified on the command line.

Patch by Arnaud Fontaine, with additional test by Mike Meyer.

1  2 
Lib/argparse.py
Lib/test/test_argparse.py
Misc/ACKS
Misc/NEWS

diff --cc Lib/argparse.py
index cc3e374a661f64dd3bddd7202b85f8754c289ca3,f77c0c2d75f942499883d160ace96e0e8f2c3201..adecb88f0eb4a546e1f43f9df90e684293e62d98
@@@ -1948,13 -1937,28 +1945,28 @@@ class ArgumentParser(_AttributeHolder, 
          # if we didn't consume all the argument strings, there were extras
          extras.extend(arg_strings[stop_index:])
  
-         # make sure all required actions were present
-         required_actions = [_get_action_name(action) for action in self._actions
-                             if action.required and action not in seen_actions]
 -        # if we didn't use all the Positional objects, there were too few
 -        # arg strings supplied.
 -        if positionals:
 -            self.error(_('too few arguments'))
 -
 -        # make sure all required actions were present, and convert defaults.
++        # make sure all required actions were present and also convert
++        # action defaults which were not given as arguments
++        required_actions = []
+         for action in self._actions:
+             if action not in seen_actions:
+                 if action.required:
 -                    name = _get_action_name(action)
 -                    self.error(_('argument %s is required') % name)
++                    required_actions.append(_get_action_name(action))
+                 else:
+                     # Convert action default now instead of doing it before
+                     # parsing arguments to avoid calling convert functions
+                     # twice (which may fail) if the argument was given, but
+                     # only if it was defined already in the namespace
+                     if (action.default is not None and
 -                            hasattr(namespace, action.dest) and
 -                            action.default is getattr(namespace, action.dest)):
++                        hasattr(namespace, action.dest) and
++                        action.default is getattr(namespace, action.dest)):
+                         setattr(namespace, action.dest,
+                                 self._get_value(action, action.default))
 +        if required_actions:
 +            self.error(_('the following arguments are required: %s') %
 +                       ', '.join(required_actions))
 +
          # make sure all required groups had one option present
          for group in self._mutually_exclusive_groups:
              if group.required:
index fe930a3e8c7b059c9d6ca04726380b2a74d9125b,cc051607e974d343448180e2ee24c9e62734c03c..72060da1967905f129d0253261c6761e0e015bcc
@@@ -4498,67 -4484,38 +4514,99 @@@ class TestArgumentTypeError(TestCase)
          else:
              self.fail()
  
 +# =========================
 +# MessageContentError tests
 +# =========================
 +
 +class TestMessageContentError(TestCase):
 +
 +    def test_missing_argument_name_in_message(self):
 +        parser = ErrorRaisingArgumentParser(prog='PROG', usage='')
 +        parser.add_argument('req_pos', type=str)
 +        parser.add_argument('-req_opt', type=int, required=True)
 +        parser.add_argument('need_one', type=str, nargs='+')
 +
 +        with self.assertRaises(ArgumentParserError) as cm:
 +            parser.parse_args([])
 +        msg = str(cm.exception)
 +        self.assertRegex(msg, 'req_pos')
 +        self.assertRegex(msg, 'req_opt')
 +        self.assertRegex(msg, 'need_one')
 +        with self.assertRaises(ArgumentParserError) as cm:
 +            parser.parse_args(['myXargument'])
 +        msg = str(cm.exception)
 +        self.assertNotIn(msg, 'req_pos')
 +        self.assertRegex(msg, 'req_opt')
 +        self.assertRegex(msg, 'need_one')
 +        with self.assertRaises(ArgumentParserError) as cm:
 +            parser.parse_args(['myXargument', '-req_opt=1'])
 +        msg = str(cm.exception)
 +        self.assertNotIn(msg, 'req_pos')
 +        self.assertNotIn(msg, 'req_opt')
 +        self.assertRegex(msg, 'need_one')
 +
 +    def test_optional_optional_not_in_message(self):
 +        parser = ErrorRaisingArgumentParser(prog='PROG', usage='')
 +        parser.add_argument('req_pos', type=str)
 +        parser.add_argument('--req_opt', type=int, required=True)
 +        parser.add_argument('--opt_opt', type=bool, nargs='?',
 +                            default=True)
 +        with self.assertRaises(ArgumentParserError) as cm:
 +            parser.parse_args([])
 +        msg = str(cm.exception)
 +        self.assertRegex(msg, 'req_pos')
 +        self.assertRegex(msg, 'req_opt')
 +        self.assertNotIn(msg, 'opt_opt')
 +        with self.assertRaises(ArgumentParserError) as cm:
 +            parser.parse_args(['--req_opt=1'])
 +        msg = str(cm.exception)
 +        self.assertRegex(msg, 'req_pos')
 +        self.assertNotIn(msg, 'req_opt')
 +        self.assertNotIn(msg, 'opt_opt')
 +
 +    def test_optional_positional_not_in_message(self):
 +        parser = ErrorRaisingArgumentParser(prog='PROG', usage='')
 +        parser.add_argument('req_pos')
 +        parser.add_argument('optional_positional', nargs='?', default='eggs')
 +        with self.assertRaises(ArgumentParserError) as cm:
 +            parser.parse_args([])
 +        msg = str(cm.exception)
 +        self.assertRegex(msg, 'req_pos')
 +        self.assertNotIn(msg, 'optional_positional')
 +
 +
+ # ================================================
+ # Check that the type function is called only once
+ # ================================================
+ class TestTypeFunctionCallOnlyOnce(TestCase):
+     def test_type_function_call_only_once(self):
+         def spam(string_to_convert):
+             self.assertEqual(string_to_convert, 'spam!')
+             return 'foo_converted'
+         parser = argparse.ArgumentParser()
+         parser.add_argument('--foo', type=spam, default='bar')
+         args = parser.parse_args('--foo spam!'.split())
+         self.assertEqual(NS(foo='foo_converted'), args)
+ # ================================================================
+ # Check that the type function is called with a non-string default
+ # ================================================================
+ class TestTypeFunctionCallWithNonStringDefault(TestCase):
+     def test_type_function_call_with_non_string_default(self):
+         def spam(int_to_convert):
+             self.assertEqual(int_to_convert, 0)
+             return 'foo_converted'
+         parser = argparse.ArgumentParser()
+         parser.add_argument('--foo', type=spam, default=0)
+         args = parser.parse_args([])
+         self.assertEqual(NS(foo='foo_converted'), args)
  # ======================
  # parse_known_args tests
  # ======================
diff --cc Misc/ACKS
Simple merge
diff --cc Misc/NEWS
index e59173d177aa93b7c649539ead74843104661fd3,c78dc96ee75b1fc4d063e5fa39ef9af21a138510..9f36080af170aa9b7d480a193c7f64e74de67aae
+++ b/Misc/NEWS
@@@ -13,91 -13,101 +13,97 @@@ Core and Builtin
  - Issue #15801: Make sure mappings passed to '%' formatting are actually
    subscriptable.
  
 -- Issue #15726: Fix incorrect bounds checking in PyState_FindModule.
 -  Patch by Robin Schreiber.
 -
 -- Issue #15604: Update uses of PyObject_IsTrue() to check for and handle
 -  errors correctly.  Patch by Serhiy Storchaka.
 +Library
 +-------
  
 -- Issue #13119: sys.stdout and sys.stderr are now using "\r\n" newline on
 -  Windows, as Python 2.
++- Issue #12776,#11839: call argparse type function (specified by add_argument)
++  only once. Before, the type function was called twice in the case where the
++  default was specified and the argument was given as well.  This was
++  especially problematic for the FileType type, as a default file would always
++  be opened, even if a file argument was specified on the command line.
 -- Issue #14579: Fix CVE-2012-2135: vulnerability in the utf-16 decoder after
 -  error handling.  Patch by Serhiy Storchaka.
 +Extension Modules
 +-----------------
  
 -- Issue #15404: Refleak in PyMethodObject repr.
 +Tests
 +-----
  
 -- Issue #15394: An issue in PyModule_Create that caused references to
 -  be leaked on some error paths has been fixed.  Patch by Julia Lawall.
 +Build
 +-----
  
 -- Issue #15368: An issue that caused bytecode generation to be
 -  non-deterministic when using randomized hashing (-R) has been fixed.
 +- Issue #15819: Make sure we can build Python out-of-tree from a readonly
 +  source directory.  (Somewhat related to Issue #9860.)
  
 -- Issue #15020: The program name used to search for Python's path is now
 -  "python3" under Unix, not "python".
 +Documentation
 +-------------
  
 -- Issue #15033: Fix the exit status bug when modules invoked using -m swith,
 -  return the proper failure return value (1). Patch contributed by Jeff Knupp.
 +- Issue #11964: Document a change in v3.2 to the behavior of the indent
 +  parameter of json encoding operations.
  
 -- Issue #12268: File readline, readlines and read() or readall() methods
 -  no longer lose data when an underlying read system call is interrupted.
 -  IOError is no longer raised due to a read system call returning EINTR
 -  from within these methods.
 +Tools/Demos
 +-----------
  
 -- Issue #15142: Fix reference leak when deallocating instances of types
 -  created using PyType_FromSpec().
  
 -- Issue #10053: Don't close FDs when FileIO.__init__ fails. Loosely based on
 -  the work by Hirokazu Yamamoto.
 +What's New in Python 3.3.0 Release Candidate 2?
 +===============================================
  
 -- Issue #14775: Fix a potential quadratic dict build-up due to the garbage
 -  collector repeatedly trying to untrack dicts.
 +*Release date: XX-Sep-2012*
  
 -- Issue #14494: Fix __future__.py and its documentation to note that
 -  absolute imports are the default behavior in 3.0 instead of 2.7.
 -  Patch by Sven Marnach.
 +Core and Builtins
 +-----------------
  
 -- Issue #14761: Fix potential leak on an error case in the import machinery.
 +- Issue #15784: Modify OSError.__str__() to better distinguish between
 +  errno error numbers and Windows error numbers.
  
 -- Issue #14699: Fix calling the classmethod descriptor directly.
 +- Issue #15781: Fix two small race conditions in import's module locking.
  
 -- Issue #14433: Prevent msvcrt crash in interactive prompt when stdin
 -  is closed.
 +Library
 +-------
  
 -- Issue #11603 (again): Setting __repr__ to __str__ now raises a RuntimeError
 -  when repr() or str() is called on such an object.
 +- Issue #15828: Restore support for C extensions in imp.load_module()
  
 -- Issue #14658: Fix binding a special method to a builtin implementation of a
 -  special method with a different name.
 +- Issue #10650: Deprecate the watchexp parameter of the Decimal.quantize()
 +  method.
  
 -- Issue #14630: Fix a memory access bug for instances of a subclass of int
 -  with value 0.
 +- Issue #15785: Modify window.get_wch() API of the curses module: return
 +  a character for most keys, and an integer for special keys, instead of
 +  always returning an integer. So it is now possible to distinguish special
 +  keys like keypad keys.
  
 -- Issue #14612: Fix jumping around with blocks by setting f_lineno.
  
 -- Issue #14607: Fix keyword-only arguments which started with ``__``.
 +What's New in Python 3.3.0 Release Candidate 1?
 +===============================================
  
 -- Issue #13889: Check and (if necessary) set FPU control word before calling
 -  any of the dtoa.c string <-> float conversion functions, on MSVC builds of
 -  Python.  This fixes issues when embedding Python in a Delphi app.
 +*Release date: 25-Aug-2012*
  
 -- Issue #14474: Save and restore exception state in thread.start_new_thread()
 -  while writing error message if the thread leaves a unhandled exception.
 +Core and Builtins
 +-----------------
  
 -- Issue #13019: Fix potential reference leaks in bytearray.extend().  Patch
 -  by Suman Saha.
 +- Issue #15573: memoryview comparisons are now performed by value with full
 +  support for any valid struct module format definition.
  
 -- Issue #14378: Fix compiling ast.ImportFrom nodes with a "__future__" string as
 -  the module name that was not interned.
 +- Issue #15316: When an item in the fromlist for __import__ doesn't exist,
 +  don't raise an error, but if an exception is raised as part of an import do
 +  let that propagate.
  
 -- Issue #14331: Use significantly less stack space when importing modules by
 -  allocating path buffers on the heap instead of the stack.
 +- Issue #15778: ensure that str(ImportError(msg)) returns a str
 +  even when msg isn't a str.
  
 -- Issue #14334: Prevent in a segfault in type.__getattribute__ when it was not
 -  passed strings.
 +- Issue #2051: Source file permission bits are once again correctly
 +  copied to the cached bytecode file. (The migration to importlib
 +  reintroduced this problem because these was no regression test. A test
 +  has been added as part of this patch)
  
 -- Issue #1469629: Allow cycles through an object's __dict__ slot to be
 -  collected. (For example if ``x.__dict__ is x``).
 +- Issue #15761: Fix crash when PYTHONEXECUTABLE is set on Mac OS X.
  
 -- Issue #14172: Fix reference leak when marshalling a buffer-like object
 -  (other than a bytes object).
 +- Issue #15726: Fix incorrect bounds checking in PyState_FindModule.
 +  Patch by Robin Schreiber.
  
 -- Issue #13521: dict.setdefault() now does only one lookup for the given key,
 -  making it "atomic" for many purposes.  Patch by Filip GruszczyƄski.
 +- Issue #15604: Update uses of PyObject_IsTrue() to check for and handle
 +  errors correctly.  Patch by Serhiy Storchaka.
  
 -- Issue #14471: Fix a possible buffer overrun in the winreg module.
 +- Issue #14846: importlib.FileFinder now handles the case where the
 +  directory being searched is removed after a previous import attempt
  
  Library
  -------