Skip to content

Commit 9100a93

Browse files
committed
Don't remove annotations from TypedDict & NamedTuple
1 parent 9e95032 commit 9100a93

3 files changed

Lines changed: 60 additions & 6 deletions

File tree

docs/source/transforms/remove_annotations.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@ Remove Annotations
33

44
This transform removes function annotations and variable annotations.
55

6-
This transform is generally safe to use. Although the annotations have no meaning to python,
7-
they are made available at runtime. If you know the module requires the annotations to be kept, disable this transform.
6+
This transform is generally safe to use. Although the annotations have no meaning to the python language,
7+
they are made available at runtime. Some python library features require annotations to be kept.
8+
9+
If these are detected, annotations are kept for that class:
10+
11+
- dataclasses.dataclass
12+
- typing.NamedTuple
13+
- typing.NamedDict
14+
15+
If you know the module requires the annotations to be kept, disable this transform.
816

917
If a variable annotation without assignment is used the annotation is changed to a literal zero instead of being removed.
1018

src/python_minifier/transforms/remove_annotations.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def is_dataclass_field(node):
7373

7474
return False
7575

76-
def is_named_tuple(node):
76+
def is_typing_sensitive(node):
7777
if sys.version_info < (3, 5):
7878
return False
7979

@@ -83,15 +83,17 @@ def is_named_tuple(node):
8383
if len(node.parent.bases) == 0:
8484
return False
8585

86+
tricky_types = ['NamedTuple', 'TypedDict']
87+
8688
for node in node.parent.bases:
87-
if isinstance(node, ast.Name) and node.id == 'NamedTuple':
89+
if isinstance(node, ast.Name) and node.id in tricky_types:
8890
return True
89-
elif isinstance(node, ast. Attribute) and node.attr == 'NamedTuple':
91+
elif isinstance(node, ast. Attribute) and node.attr in tricky_types:
9092
return True
9193

9294
return False
9395

94-
if is_dataclass_field(node) or is_named_tuple(node):
96+
if is_dataclass_field(node) or is_typing_sensitive(node):
9597
return node
9698
elif node.value:
9799
return self.add_child(ast.Assign([node.target], node.value), parent=node.parent)

test/test_remove_annotations.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,47 @@ class Dummy(typing.NermedTupel):
157157
expected_ast = ast.parse(expected)
158158
actual_ast = remove_annotations(source)
159159
compare_ast(expected_ast, actual_ast)
160+
161+
162+
def test_no_remove_typeddict():
163+
if sys.version_info < (3, 8):
164+
pytest.skip('annotations unavailable in python < 3.6')
165+
166+
source = '''
167+
class Dummy(TypedDict):
168+
myfield: int
169+
mysecondfile: str
170+
171+
class Dummy(HypedDict):
172+
myfield: int
173+
mysecondfile: str
174+
175+
class Dummy(typing.TypedDict):
176+
myfield: int
177+
mysecondfile: str
178+
179+
class Dummy(typing.TypedDic):
180+
myfield: int
181+
mysecondfile: str
182+
'''
183+
expected = '''
184+
class Dummy(TypedDict):
185+
myfield: int
186+
mysecondfile: str
187+
188+
class Dummy(HypedDict):
189+
myfield: 0
190+
mysecondfile: 0
191+
192+
class Dummy(typing.TypedDict):
193+
myfield: int
194+
mysecondfile: str
195+
196+
class Dummy(typing.TypedDic):
197+
myfield: 0
198+
mysecondfile: 0
199+
200+
'''
201+
expected_ast = ast.parse(expected)
202+
actual_ast = remove_annotations(source)
203+
compare_ast(expected_ast, actual_ast)

0 commit comments

Comments
 (0)