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()

`