Skip to content

Commit

Permalink
Feat: add initial support for TypedDict
Browse files Browse the repository at this point in the history
`TypedDicts` are used only within the type system for analysis and
represent the keys of a dictionary and their corresponding types.
They are a useful tool when providing type hints of a JSON payload or
similar.

For example the following code snippet will raise a false-positive alert
from this flake8 plug-in:
```python
class ApiPage(TypedDict):
  url: str
  next: str
  data: list[dict[str, Any]]
```

This is because a `TypedDict` is analysed as if it was a standard class.
Since it is only used in the type system it is safe to ignore anything
that inherits from `TypedDict` in this plugin.
  • Loading branch information
danjones1618 committed Jun 1, 2024
1 parent 5484162 commit ab939c7
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 1 deletion.
4 changes: 3 additions & 1 deletion flake8_builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ def run(self):

def check_assignment(self, statement):
msg = self.assign_msg
if type(statement.__flake8_builtins_parent) is ast.ClassDef:
if isinstance(statement.__flake8_builtins_parent, ast.ClassDef):
if "TypedDict" in {a.id for a in statement.__flake8_builtins_parent.bases if isinstance(a, ast.Name)}:
return
msg = self.class_attribute_msg

if isinstance(statement, ast.Assign):
Expand Down
74 changes: 74 additions & 0 deletions run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,77 @@ def test_module_name_ignore_module():
def test_module_name_not_builtin():
source = ''
check_code(source, filename='log_config')



@pytest.mark.skipif(
sys.version_info < (3, 9),
reason='Typed dicts are introduced in Python 3.9',
)
def test_typed_dicts_dont_shadow_builtins():
source = """
from typing import Any, TypedDict
class PaginatedResponse(TypedDict):
count: int
next: str
prev: str
data: list[Any]
"""
check_code(source)


@pytest.mark.xfail(reason="N-deep inheritence not supported")
@pytest.mark.skipif(
sys.version_info < (3, 9),
reason='Typed dicts are introduced in Python 3.9',
)
def test_inherited_typed_dicts_dont_shadow_builtins():
source = """
from typing import Any, TypedDict
class ApiResponseDict(TypedDict):
status: int
okay: bool
class PaginatedResponse(ApiResponseDict):
count: int
next: str
prev: str
data: list[Any]
"""
check_code(source)


@pytest.mark.xfail(reason="N-deep inheritence not supported")
@pytest.mark.skipif(
sys.version_info < (3, 9),
reason='Typed dicts are introduced in Python 3.9',
)
def test_n_deep_inherited_typed_dicts_dont_shadow_builtins():
source = """
from typing import Any, TypedDict
class ApiResponseDict(TypedDict):
status: int
okay: bool
class Something(ApiResponseDict):
okay: int
class SomethingElse(Something):
is_an_oof: bool
class PaginatedResponse(SomethingElse):
count: int
next: str
prev: str
data: list[Any]
"""
check_code(source)

0 comments on commit ab939c7

Please sign in to comment.