Skip to content

Commit 8fe98cc

Browse files
authored
Release v2.5.0
2 parents 8df66f4 + e741bd6 commit 8fe98cc

File tree

15 files changed

+527
-379
lines changed

15 files changed

+527
-379
lines changed

.dependabot/config.yml renamed to .github/dependabot.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
version: 1
1+
version: 2
22

3-
update_configs:
4-
- package_manager: "python"
3+
updates:
4+
- package_manager: "pip"
55
directory: "/"
66
update_schedule: "weekly"
7+
target-branch: "dev-next"
78
ignored_updates:
89
# Ignore dev dependencies, these are pinned fairly liberally so they don't need to be
910
# rigorously tracked for updates

.github/workflows/lint_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
runs-on: ubuntu-latest
3434
strategy:
3535
matrix:
36-
python-version: [3.6, 3.7, 3.8, 3.9-dev]
36+
python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev]
3737
fail-fast: false
3838

3939
steps:

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ repos:
1313
hooks:
1414
- id: black
1515
- repo: https://github.com/pre-commit/pre-commit-hooks
16-
rev: v3.2.0
16+
rev: v3.4.0
1717
hooks:
1818
- id: check-merge-conflict
1919
- id: check-toml
2020
- id: check-yaml
2121
- id: end-of-file-fixer
2222
- id: mixed-line-ending
2323
- repo: https://github.com/pre-commit/pygrep-hooks
24-
rev: v1.6.0
24+
rev: v1.7.0
2525
hooks:
2626
- id: python-check-blanket-noqa

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Changelog
22
Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (`<major>`.`<minor>`.`<patch>`)
33

4+
## [v2.5.0]
5+
### Added
6+
* #103 add `--allow-untyped-nested` to suppress all errors from dynamically typted nested functions. A function is considered dynamically typed if it does not contain any type hints.
7+
48
## [v2.4.1]
59
### Fixed
610
* #100 Fix incorrect positioning of posonlyargs in the `Function` argument list, causing incorrect classification of the type of missing argument.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020 sco1
3+
Copyright (c) 2019-2021 sco1
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ You can verify it's being picked up by invoking the following in your shell:
1919

2020
```bash
2121
$ flake8 --version
22-
3.8.3 (flake8-annotations: 2.4.1, mccabe: 0.6.1, pycodestyle: 2.6.0, pyflakes: 2.2.0) CPython 3.8.2 on Darwin
22+
3.8.4 (flake8-annotations: 2.5.0, mccabe: 0.6.1, pycodestyle: 2.6.0, pyflakes: 2.2.0) CPython 3.9.0 on Darwin
2323
```
2424

2525
## Table of Warnings
@@ -76,6 +76,11 @@ Suppress all errors for dynamically typed functions. A function is considered dy
7676

7777
Default: `False`
7878

79+
### `--allow-untyped-nested`: `bool`
80+
Suppress all errors for dynamically typed nested functions. A function is considered dynamically typed if it does not contain any type hints.
81+
82+
Default: `False`
83+
7984
### `--mypy-init-return`: `bool`
8085
Allow omission of a return type hint for `__init__` if at least one argument is annotated. See [mypy's documentation](https://mypy.readthedocs.io/en/stable/class_basics.html?#annotating-init-methods) for additional details.
8186

flake8_annotations/__init__.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
PY_GTE_38 = False
1919

20-
__version__ = "2.4.1"
20+
__version__ = "2.5.0"
2121

2222
# The order of AST_ARG_TYPES must match Python's grammar
2323
# See: https://docs.python.org/3/library/ast.html#abstract-grammar
@@ -114,6 +114,7 @@ def __init__(
114114
has_type_comment: bool = False,
115115
has_only_none_returns: bool = True,
116116
is_overload_decorated: bool = False,
117+
is_nested: bool = False,
117118
args: List[Argument] = None,
118119
):
119120
self.name = name
@@ -126,6 +127,7 @@ def __init__(
126127
self.has_type_comment = has_type_comment
127128
self.has_only_none_returns = has_only_none_returns
128129
self.is_overload_decorated = is_overload_decorated
130+
self.is_nested = is_nested
129131
self.args = args
130132

131133
def is_fully_annotated(self) -> bool:
@@ -175,6 +177,7 @@ def __repr__(self) -> str:
175177
f"has_type_comment={self.has_type_comment}, "
176178
f"has_only_none_returns={self.has_only_none_returns}, "
177179
f"is_overload_decorated={self.is_overload_decorated}, "
180+
f"is_nested={self.is_nested}, "
178181
f"args={self.args}"
179182
")"
180183
)
@@ -268,7 +271,7 @@ def colon_seeker(node: AST_FUNCTION_TYPES, lines: List[str]) -> Tuple[int, int]:
268271
# the function node's body begins.
269272
# If the docstring is on one line then no rewinding is necessary.
270273
n_triple_quotes = lines[def_end_lineno].count('"""')
271-
if n_triple_quotes == 1:
274+
if n_triple_quotes == 1: # pragma: no branch
272275
# Docstring closure, rewind until the opening is found & take the line prior
273276
while True:
274277
def_end_lineno -= 1
@@ -431,6 +434,8 @@ def has_overload_decorator(function_node: AST_FUNCTION_TYPES) -> bool:
431434
class FunctionVisitor(ast.NodeVisitor):
432435
"""An ast.NodeVisitor instance for walking the AST and describing all contained functions."""
433436

437+
AST_FUNC_TYPES = (ast.FunctionDef, ast.AsyncFunctionDef)
438+
434439
def __init__(self, lines: List[str]):
435440
self.lines = lines
436441
self.function_definitions: List[Function] = []
@@ -445,13 +450,19 @@ def switch_context(self, node: AST_DEF_NODES) -> None:
445450
446451
Thank you for the inspiration @isidentical :)
447452
"""
448-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
453+
if isinstance(node, self.AST_FUNC_TYPES):
449454
# Check for non-empty context first to prevent IndexErrors for non-nested nodes
450-
if self._context and isinstance(self._context[-1], ast.ClassDef):
451-
# Check if current context is a ClassDef node & pass the appropriate flag
452-
self.function_definitions.append(
453-
Function.from_function_node(node, self.lines, is_class_method=True)
454-
)
455+
if self._context:
456+
if isinstance(self._context[-1], ast.ClassDef):
457+
# Check if current context is a ClassDef node & pass the appropriate flag
458+
self.function_definitions.append(
459+
Function.from_function_node(node, self.lines, is_class_method=True)
460+
)
461+
elif isinstance(self._context[-1], self.AST_FUNC_TYPES): # pragma: no branch
462+
# Check for nested function & pass the appropriate flag
463+
self.function_definitions.append(
464+
Function.from_function_node(node, self.lines, is_nested=True)
465+
)
455466
else:
456467
self.function_definitions.append(Function.from_function_node(node, self.lines))
457468

@@ -501,7 +512,7 @@ def visit_Return(self, node: ast.Return) -> None:
501512
# In the event of an explicit `None` return (`return None`), the node body will be an
502513
# instance of either `ast.Constant` (3.8+) or `ast.NameConstant`, which we need to check
503514
# to see if it's actually `None`
504-
if isinstance(node.value, (ast.Constant, ast.NameConstant)):
515+
if isinstance(node.value, (ast.Constant, ast.NameConstant)): # pragma: no branch
505516
if node.value.value is None:
506517
return
507518

flake8_annotations/checker.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(self, tree: ast.Module, lines: List[str]):
3939
self.suppress_none_returning: bool
4040
self.suppress_dummy_args: bool
4141
self.allow_untyped_defs: bool
42+
self.allow_untyped_nested: bool
4243
self.mypy_init_return: bool
4344

4445
def run(self) -> Generator[FORMATTED_ERROR, None, None]:
@@ -61,9 +62,13 @@ def run(self) -> Generator[FORMATTED_ERROR, None, None]:
6162
#
6263
# Flake8 handles all noqa and error code ignore configurations after the error is yielded
6364
for function in visitor.function_definitions:
64-
if self.allow_untyped_defs and function.is_dynamically_typed():
65-
# Skip yielding errors from dynamically typed functions
66-
continue
65+
if function.is_dynamically_typed():
66+
if self.allow_untyped_defs:
67+
# Skip yielding errors from dynamically typed functions
68+
continue
69+
elif function.is_nested and self.allow_untyped_nested:
70+
# Skip yielding errors from dynamically typed nested functions
71+
continue
6772

6873
# Create sentinels to check for mixed hint styles
6974
if function.has_type_comment:
@@ -153,6 +158,14 @@ def add_options(cls, parser: OptionManager) -> None: # pragma: no cover
153158
help="Suppress all errors for dynamically typed functions. (Default: False)",
154159
)
155160

161+
parser.add_option(
162+
"--allow-untyped-nested",
163+
default=False,
164+
action="store_true",
165+
parse_from_config=True,
166+
help="Suppress all errors for dynamically typed nested functions. (Default: False)",
167+
)
168+
156169
parser.add_option(
157170
"--mypy-init-return",
158171
default=False,
@@ -170,6 +183,7 @@ def parse_options(cls, options: Namespace) -> None: # pragma: no cover
170183
cls.suppress_none_returning = options.suppress_none_returning
171184
cls.suppress_dummy_args = options.suppress_dummy_args
172185
cls.allow_untyped_defs = options.allow_untyped_defs
186+
cls.allow_untyped_nested = options.allow_untyped_nested
173187
cls.mypy_init_return = options.mypy_init_return
174188

175189
@staticmethod

0 commit comments

Comments
 (0)