From: R David Murray Date: Sat, 1 Sep 2012 03:09:34 +0000 (-0400) Subject: Merge #12776,#11839: call argparse type function only once. X-Git-Tag: v3.3.1rc1~818^2^2~161 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=64b0ef15097017b20b1edca1d3bc1e38d51feb10;p=thirdparty%2FPython%2Fcpython.git Merge #12776,#11839: call argparse type function 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. Patch by Arnaud Fontaine, with additional test by Mike Meyer. --- 64b0ef15097017b20b1edca1d3bc1e38d51feb10 diff --cc Lib/argparse.py index cc3e374a661f,f77c0c2d75f9..adecb88f0eb4 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@@ -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: diff --cc Lib/test/test_argparse.py index fe930a3e8c7b,cc051607e974..72060da19679 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@@ -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/NEWS index e59173d177aa,c78dc96ee75b..9f36080af170 --- a/Misc/NEWS +++ 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 -------