|
1 | 1 | import string |
2 | 2 | import sys |
3 | 3 | import unittest |
| 4 | +from functools import partial |
4 | 5 | from test import support |
5 | 6 | from test.support import import_helper |
6 | 7 | from test.support import script_helper |
@@ -825,6 +826,80 @@ def __hash__(self): |
825 | 826 | getargs_keyword_only(1, 2, **{BadStr("monster"): 666}) |
826 | 827 |
|
827 | 828 |
|
| 829 | +class RequiredKeywordOnly_TestCase(unittest.TestCase): |
| 830 | + # '%' marks the start of the keyword-only arguments, which are required |
| 831 | + # until '|', so that required and optional ones can be mixed. |
| 832 | + |
| 833 | + def parse(self, format, args, kwargs, keywords=('a', 'b', 'c', 'd')): |
| 834 | + return _testcapi.parse_tuple_and_keywords(args, kwargs, |
| 835 | + format, list(keywords)) |
| 836 | + |
| 837 | + def test_required_after_optional(self): |
| 838 | + # f(a, b=None, *, c, d=None) |
| 839 | + parse = partial(self.parse, "O|O%O|O") |
| 840 | + self.assertEqual(parse((1, 2), {'c': 3, 'd': 4}), (1, 2, 3, 4)) |
| 841 | + self.assertEqual(parse((1,), {'c': 3}), (1, None, 3, None)) |
| 842 | + with self.assertRaisesRegex(TypeError, "missing required argument 'c'"): |
| 843 | + parse((1,), {}) |
| 844 | + with self.assertRaisesRegex(TypeError, "missing required argument 'c'"): |
| 845 | + parse((1,), {'d': 4}) |
| 846 | + with self.assertRaisesRegex(TypeError, "at most 2 positional"): |
| 847 | + parse((1, 2, 3), {'c': 3}) |
| 848 | + |
| 849 | + def test_all_keyword_only_required(self): |
| 850 | + # f(a, b=None, *, c, d) |
| 851 | + parse = partial(self.parse, "O|O%OO") |
| 852 | + self.assertEqual(parse((1,), {'c': 3, 'd': 4}), (1, None, 3, 4)) |
| 853 | + with self.assertRaisesRegex(TypeError, "missing required argument 'd'"): |
| 854 | + parse((1,), {'c': 3}) |
| 855 | + |
| 856 | + def test_all_positional_required(self): |
| 857 | + # f(a, b, *, c, d=None) |
| 858 | + parse = partial(self.parse, "OO%O|O") |
| 859 | + self.assertEqual(parse((1, 2), {'c': 3}), (1, 2, 3, None)) |
| 860 | + with self.assertRaisesRegex(TypeError, "missing required argument 'c'"): |
| 861 | + parse((1, 2), {}) |
| 862 | + |
| 863 | + def test_cached_parser(self): |
| 864 | + # The same format, parsed once and cached in a _PyArg_Parser. |
| 865 | + f = _testcapi.getargs_fast_required_kwonly |
| 866 | + self.assertEqual(f(1, 2, c=3, d=4), (1, 2, 3, 4)) |
| 867 | + self.assertEqual(f(1, c=3), (1, None, 3, None)) |
| 868 | + with self.assertRaisesRegex(TypeError, "missing required argument 'c'"): |
| 869 | + f(1) |
| 870 | + with self.assertRaisesRegex(TypeError, "missing required argument 'c'"): |
| 871 | + f(1, d=4) |
| 872 | + |
| 873 | + def test_invalid_format(self): |
| 874 | + for format, msg in ( |
| 875 | + ("O%O%O", r"\$ specified twice"), |
| 876 | + ("O%O$O", r"\$ specified twice"), |
| 877 | + ("O%O|O|O", r"\| specified twice"), |
| 878 | + ("O$O|O", r"\$ before \|"), |
| 879 | + ): |
| 880 | + with self.subTest(format=format): |
| 881 | + n = format.count('O') |
| 882 | + npos = len(format) - len(format.lstrip('O')) |
| 883 | + args = tuple(range(npos)) |
| 884 | + kwargs = {'abcd'[i]: i for i in range(npos, n)} |
| 885 | + with self.assertRaisesRegex(SystemError, msg): |
| 886 | + self.parse(format, args, kwargs, 'abcd'[:n]) |
| 887 | + |
| 888 | + def test_unchanged_meaning_of_dollar(self): |
| 889 | + # '$' still inherits the state of the positional arguments. |
| 890 | + parse = partial(self.parse, "O|O$O", keywords=('a', 'b', 'c')) |
| 891 | + self.assertEqual(parse((1,), {}), (1, None, None)) |
| 892 | + parse = partial(self.parse, "OO$O", keywords=('a', 'b', 'c')) |
| 893 | + self.assertEqual(parse((1, 2), {'c': 3}), (1, 2, 3)) |
| 894 | + with self.assertRaisesRegex(TypeError, "missing required argument 'c'"): |
| 895 | + parse((1, 2), {}) |
| 896 | + # The same with a cached parser. |
| 897 | + f = _testcapi.getargs_fast_kwonly |
| 898 | + self.assertEqual(f(1, 2, c=3, d=4), (1, 2, 3, 4)) |
| 899 | + with self.assertRaisesRegex(TypeError, "missing required argument 'c'"): |
| 900 | + f(1, 2) |
| 901 | + |
| 902 | + |
828 | 903 | class PositionalOnlyAndKeywords_TestCase(unittest.TestCase): |
829 | 904 | from _testcapi import getargs_positional_only_and_keywords as getargs |
830 | 905 |
|
@@ -1164,8 +1239,8 @@ def test_skipitem(self): |
1164 | 1239 |
|
1165 | 1240 | # skip parentheses, the error reporting is inconsistent about them |
1166 | 1241 | # skip 'e' and 'w', they're always two-character codes |
1167 | | - # skip '|' and '$', they don't represent arguments anyway |
1168 | | - if c in '()ew|$': |
| 1242 | + # skip '|', '$' and '%', they don't represent arguments anyway |
| 1243 | + if c in '()ew|$%': |
1169 | 1244 | continue |
1170 | 1245 |
|
1171 | 1246 | # test the format unit when not skipped |
|
0 commit comments