bpo-33536: Validate make_dataclass() field names. (GH-6906) · python/cpython@6409e75 (original) (raw)
`@@ -1826,114 +1826,6 @@ class R:
`
1826
1826
`self.assertEqual(new_sample.x, another_new_sample.x)
`
1827
1827
`self.assertEqual(sample.y, another_new_sample.y)
`
1828
1828
``
1829
``
`-
def test_helper_make_dataclass(self):
`
1830
``
`-
C = make_dataclass('C',
`
1831
``
`-
[('x', int),
`
1832
``
`-
('y', int, field(default=5))],
`
1833
``
`-
namespace={'add_one': lambda self: self.x + 1})
`
1834
``
`-
c = C(10)
`
1835
``
`-
self.assertEqual((c.x, c.y), (10, 5))
`
1836
``
`-
self.assertEqual(c.add_one(), 11)
`
1837
``
-
1838
``
-
1839
``
`-
def test_helper_make_dataclass_no_mutate_namespace(self):
`
1840
``
`-
Make sure a provided namespace isn't mutated.
`
1841
``
`-
ns = {}
`
1842
``
`-
C = make_dataclass('C',
`
1843
``
`-
[('x', int),
`
1844
``
`-
('y', int, field(default=5))],
`
1845
``
`-
namespace=ns)
`
1846
``
`-
self.assertEqual(ns, {})
`
1847
``
-
1848
``
`-
def test_helper_make_dataclass_base(self):
`
1849
``
`-
class Base1:
`
1850
``
`-
pass
`
1851
``
`-
class Base2:
`
1852
``
`-
pass
`
1853
``
`-
C = make_dataclass('C',
`
1854
``
`-
[('x', int)],
`
1855
``
`-
bases=(Base1, Base2))
`
1856
``
`-
c = C(2)
`
1857
``
`-
self.assertIsInstance(c, C)
`
1858
``
`-
self.assertIsInstance(c, Base1)
`
1859
``
`-
self.assertIsInstance(c, Base2)
`
1860
``
-
1861
``
`-
def test_helper_make_dataclass_base_dataclass(self):
`
1862
``
`-
@dataclass
`
1863
``
`-
class Base1:
`
1864
``
`-
x: int
`
1865
``
`-
class Base2:
`
1866
``
`-
pass
`
1867
``
`-
C = make_dataclass('C',
`
1868
``
`-
[('y', int)],
`
1869
``
`-
bases=(Base1, Base2))
`
1870
``
`-
with self.assertRaisesRegex(TypeError, 'required positional'):
`
1871
``
`-
c = C(2)
`
1872
``
`-
c = C(1, 2)
`
1873
``
`-
self.assertIsInstance(c, C)
`
1874
``
`-
self.assertIsInstance(c, Base1)
`
1875
``
`-
self.assertIsInstance(c, Base2)
`
1876
``
-
1877
``
`-
self.assertEqual((c.x, c.y), (1, 2))
`
1878
``
-
1879
``
`-
def test_helper_make_dataclass_init_var(self):
`
1880
``
`-
def post_init(self, y):
`
1881
``
`-
self.x *= y
`
1882
``
-
1883
``
`-
C = make_dataclass('C',
`
1884
``
`-
[('x', int),
`
1885
``
`-
('y', InitVar[int]),
`
1886
``
`-
],
`
1887
``
`-
namespace={'post_init': post_init},
`
1888
``
`-
)
`
1889
``
`-
c = C(2, 3)
`
1890
``
`-
self.assertEqual(vars(c), {'x': 6})
`
1891
``
`-
self.assertEqual(len(fields(c)), 1)
`
1892
``
-
1893
``
`-
def test_helper_make_dataclass_class_var(self):
`
1894
``
`-
C = make_dataclass('C',
`
1895
``
`-
[('x', int),
`
1896
``
`-
('y', ClassVar[int], 10),
`
1897
``
`-
('z', ClassVar[int], field(default=20)),
`
1898
``
`-
])
`
1899
``
`-
c = C(1)
`
1900
``
`-
self.assertEqual(vars(c), {'x': 1})
`
1901
``
`-
self.assertEqual(len(fields(c)), 1)
`
1902
``
`-
self.assertEqual(C.y, 10)
`
1903
``
`-
self.assertEqual(C.z, 20)
`
1904
``
-
1905
``
`-
def test_helper_make_dataclass_other_params(self):
`
1906
``
`-
C = make_dataclass('C',
`
1907
``
`-
[('x', int),
`
1908
``
`-
('y', ClassVar[int], 10),
`
1909
``
`-
('z', ClassVar[int], field(default=20)),
`
1910
``
`-
],
`
1911
``
`-
init=False)
`
1912
``
`-
Make sure we have a repr, but no init.
`
1913
``
`-
self.assertNotIn('init', vars(C))
`
1914
``
`-
self.assertIn('repr', vars(C))
`
1915
``
-
1916
``
`-
Make sure random other params don't work.
`
1917
``
`-
with self.assertRaisesRegex(TypeError, 'unexpected keyword argument'):
`
1918
``
`-
C = make_dataclass('C',
`
1919
``
`-
[],
`
1920
``
`-
xxinit=False)
`
1921
``
-
1922
``
`-
def test_helper_make_dataclass_no_types(self):
`
1923
``
`-
C = make_dataclass('Point', ['x', 'y', 'z'])
`
1924
``
`-
c = C(1, 2, 3)
`
1925
``
`-
self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
`
1926
``
`-
self.assertEqual(C.annotations, {'x': 'typing.Any',
`
1927
``
`-
'y': 'typing.Any',
`
1928
``
`-
'z': 'typing.Any'})
`
1929
``
-
1930
``
`-
C = make_dataclass('Point', ['x', ('y', int), 'z'])
`
1931
``
`-
c = C(1, 2, 3)
`
1932
``
`-
self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
`
1933
``
`-
self.assertEqual(C.annotations, {'x': 'typing.Any',
`
1934
``
`-
'y': int,
`
1935
``
`-
'z': 'typing.Any'})
`
1936
``
-
1937
1829
``
1938
1830
`class TestFieldNoAnnotation(unittest.TestCase):
`
1939
1831
`def test_field_without_annotation(self):
`
`@@ -2947,5 +2839,170 @@ def test_classvar_module_level_import(self):
`
2947
2839
`self.assertNotIn('not_iv4', c.dict)
`
2948
2840
``
2949
2841
``
``
2842
`+
class TestMakeDataclass(unittest.TestCase):
`
``
2843
`+
def test_simple(self):
`
``
2844
`+
C = make_dataclass('C',
`
``
2845
`+
[('x', int),
`
``
2846
`+
('y', int, field(default=5))],
`
``
2847
`+
namespace={'add_one': lambda self: self.x + 1})
`
``
2848
`+
c = C(10)
`
``
2849
`+
self.assertEqual((c.x, c.y), (10, 5))
`
``
2850
`+
self.assertEqual(c.add_one(), 11)
`
``
2851
+
``
2852
+
``
2853
`+
def test_no_mutate_namespace(self):
`
``
2854
`+
Make sure a provided namespace isn't mutated.
`
``
2855
`+
ns = {}
`
``
2856
`+
C = make_dataclass('C',
`
``
2857
`+
[('x', int),
`
``
2858
`+
('y', int, field(default=5))],
`
``
2859
`+
namespace=ns)
`
``
2860
`+
self.assertEqual(ns, {})
`
``
2861
+
``
2862
`+
def test_base(self):
`
``
2863
`+
class Base1:
`
``
2864
`+
pass
`
``
2865
`+
class Base2:
`
``
2866
`+
pass
`
``
2867
`+
C = make_dataclass('C',
`
``
2868
`+
[('x', int)],
`
``
2869
`+
bases=(Base1, Base2))
`
``
2870
`+
c = C(2)
`
``
2871
`+
self.assertIsInstance(c, C)
`
``
2872
`+
self.assertIsInstance(c, Base1)
`
``
2873
`+
self.assertIsInstance(c, Base2)
`
``
2874
+
``
2875
`+
def test_base_dataclass(self):
`
``
2876
`+
@dataclass
`
``
2877
`+
class Base1:
`
``
2878
`+
x: int
`
``
2879
`+
class Base2:
`
``
2880
`+
pass
`
``
2881
`+
C = make_dataclass('C',
`
``
2882
`+
[('y', int)],
`
``
2883
`+
bases=(Base1, Base2))
`
``
2884
`+
with self.assertRaisesRegex(TypeError, 'required positional'):
`
``
2885
`+
c = C(2)
`
``
2886
`+
c = C(1, 2)
`
``
2887
`+
self.assertIsInstance(c, C)
`
``
2888
`+
self.assertIsInstance(c, Base1)
`
``
2889
`+
self.assertIsInstance(c, Base2)
`
``
2890
+
``
2891
`+
self.assertEqual((c.x, c.y), (1, 2))
`
``
2892
+
``
2893
`+
def test_init_var(self):
`
``
2894
`+
def post_init(self, y):
`
``
2895
`+
self.x *= y
`
``
2896
+
``
2897
`+
C = make_dataclass('C',
`
``
2898
`+
[('x', int),
`
``
2899
`+
('y', InitVar[int]),
`
``
2900
`+
],
`
``
2901
`+
namespace={'post_init': post_init},
`
``
2902
`+
)
`
``
2903
`+
c = C(2, 3)
`
``
2904
`+
self.assertEqual(vars(c), {'x': 6})
`
``
2905
`+
self.assertEqual(len(fields(c)), 1)
`
``
2906
+
``
2907
`+
def test_class_var(self):
`
``
2908
`+
C = make_dataclass('C',
`
``
2909
`+
[('x', int),
`
``
2910
`+
('y', ClassVar[int], 10),
`
``
2911
`+
('z', ClassVar[int], field(default=20)),
`
``
2912
`+
])
`
``
2913
`+
c = C(1)
`
``
2914
`+
self.assertEqual(vars(c), {'x': 1})
`
``
2915
`+
self.assertEqual(len(fields(c)), 1)
`
``
2916
`+
self.assertEqual(C.y, 10)
`
``
2917
`+
self.assertEqual(C.z, 20)
`
``
2918
+
``
2919
`+
def test_other_params(self):
`
``
2920
`+
C = make_dataclass('C',
`
``
2921
`+
[('x', int),
`
``
2922
`+
('y', ClassVar[int], 10),
`
``
2923
`+
('z', ClassVar[int], field(default=20)),
`
``
2924
`+
],
`
``
2925
`+
init=False)
`
``
2926
`+
Make sure we have a repr, but no init.
`
``
2927
`+
self.assertNotIn('init', vars(C))
`
``
2928
`+
self.assertIn('repr', vars(C))
`
``
2929
+
``
2930
`+
Make sure random other params don't work.
`
``
2931
`+
with self.assertRaisesRegex(TypeError, 'unexpected keyword argument'):
`
``
2932
`+
C = make_dataclass('C',
`
``
2933
`+
[],
`
``
2934
`+
xxinit=False)
`
``
2935
+
``
2936
`+
def test_no_types(self):
`
``
2937
`+
C = make_dataclass('Point', ['x', 'y', 'z'])
`
``
2938
`+
c = C(1, 2, 3)
`
``
2939
`+
self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
`
``
2940
`+
self.assertEqual(C.annotations, {'x': 'typing.Any',
`
``
2941
`+
'y': 'typing.Any',
`
``
2942
`+
'z': 'typing.Any'})
`
``
2943
+
``
2944
`+
C = make_dataclass('Point', ['x', ('y', int), 'z'])
`
``
2945
`+
c = C(1, 2, 3)
`
``
2946
`+
self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
`
``
2947
`+
self.assertEqual(C.annotations, {'x': 'typing.Any',
`
``
2948
`+
'y': int,
`
``
2949
`+
'z': 'typing.Any'})
`
``
2950
+
``
2951
`+
def test_invalid_type_specification(self):
`
``
2952
`+
for bad_field in [(),
`
``
2953
`+
(1, 2, 3, 4),
`
``
2954
`+
]:
`
``
2955
`+
with self.subTest(bad_field=bad_field):
`
``
2956
`+
with self.assertRaisesRegex(TypeError, r'Invalid field: '):
`
``
2957
`+
make_dataclass('C', ['a', bad_field])
`
``
2958
+
``
2959
`+
And test for things with no len().
`
``
2960
`+
for bad_field in [float,
`
``
2961
`+
lambda x:x,
`
``
2962
`+
]:
`
``
2963
`+
with self.subTest(bad_field=bad_field):
`
``
2964
`+
with self.assertRaisesRegex(TypeError, r'has no len()'):
`
``
2965
`+
make_dataclass('C', ['a', bad_field])
`
``
2966
+
``
2967
`+
def test_duplicate_field_names(self):
`
``
2968
`+
for field in ['a', 'ab']:
`
``
2969
`+
with self.subTest(field=field):
`
``
2970
`+
with self.assertRaisesRegex(TypeError, 'Field name duplicated'):
`
``
2971
`+
make_dataclass('C', [field, 'a', field])
`
``
2972
+
``
2973
`+
def test_keyword_field_names(self):
`
``
2974
`+
for field in ['for', 'async', 'await', 'as']:
`
``
2975
`+
with self.subTest(field=field):
`
``
2976
`+
with self.assertRaisesRegex(TypeError, 'must not be keywords'):
`
``
2977
`+
make_dataclass('C', ['a', field])
`
``
2978
`+
with self.assertRaisesRegex(TypeError, 'must not be keywords'):
`
``
2979
`+
make_dataclass('C', [field])
`
``
2980
`+
with self.assertRaisesRegex(TypeError, 'must not be keywords'):
`
``
2981
`+
make_dataclass('C', [field, 'a'])
`
``
2982
+
``
2983
`+
def test_non_identifier_field_names(self):
`
``
2984
`+
for field in ['()', 'x,y', '*', '2@3', '', 'little johnny tables']:
`
``
2985
`+
with self.subTest(field=field):
`
``
2986
`+
with self.assertRaisesRegex(TypeError, 'must be valid identifers'):
`
``
2987
`+
make_dataclass('C', ['a', field])
`
``
2988
`+
with self.assertRaisesRegex(TypeError, 'must be valid identifers'):
`
``
2989
`+
make_dataclass('C', [field])
`
``
2990
`+
with self.assertRaisesRegex(TypeError, 'must be valid identifers'):
`
``
2991
`+
make_dataclass('C', [field, 'a'])
`
``
2992
+
``
2993
`+
def test_underscore_field_names(self):
`
``
2994
`+
Unlike namedtuple, it's okay if dataclass field names have
`
``
2995
`+
an underscore.
`
``
2996
`+
make_dataclass('C', ['_', 'a', 'a_a', 'a'])
`
``
2997
+
``
2998
`+
def test_funny_class_names_names(self):
`
``
2999
`+
No reason to prevent weird class names, since
`
``
3000
`+
types.new_class allows them.
`
``
3001
`+
for classname in ['()', 'x,y', '*', '2@3', '']:
`
``
3002
`+
with self.subTest(classname=classname):
`
``
3003
`+
C = make_dataclass(classname, ['a', 'b'])
`
``
3004
`+
self.assertEqual(C.name, classname)
`
``
3005
+
``
3006
+
2950
3007
`if name == 'main':
`
2951
3008
`unittest.main()
`