|
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 | + # A single "$" 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 |
|
@@ -1269,8 +1344,6 @@ def test_bad_use(self): |
1269 | 1344 | (1,), {}, '||O', ['a']) |
1270 | 1345 | self.assertRaises(SystemError, _testcapi.parse_tuple_and_keywords, |
1271 | 1346 | (1, 2), {}, '|O|O', ['a', 'b']) |
1272 | | - self.assertRaises(SystemError, _testcapi.parse_tuple_and_keywords, |
1273 | | - (), {'a': 1}, '$$O', ['a']) |
1274 | 1347 | self.assertRaises(SystemError, _testcapi.parse_tuple_and_keywords, |
1275 | 1348 | (), {'a': 1, 'b': 2}, '$O$O', ['a', 'b']) |
1276 | 1349 | self.assertRaises(SystemError, _testcapi.parse_tuple_and_keywords, |
|
0 commit comments