Skip to content
This repository was archived by the owner on Feb 19, 2023. It is now read-only.

Commit f131a47

Browse files
committed
wip
1 parent edef1a3 commit f131a47

29 files changed

+461
-177
lines changed

Diff for: pandas_style_guide/__main__.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
from __future__ import annotations
2-
import tokenize
3-
import os
42

53
import ast
64
import importlib.metadata
7-
from typing import Any, List, Dict, Tuple, Type, Callable
8-
import collections
9-
from typing import Generator
5+
import os
6+
from typing import Any, Generator
107

118
from pandas_style_guide._data import FUNCS, visit
129

1310

14-
15-
1611
class Plugin:
1712
name = os.path.split(os.path.dirname(__file__))[-1]
1813
version = importlib.metadata.version(name)
@@ -26,4 +21,3 @@ def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
2621
return
2722
for line, col, msg in callbacks:
2823
yield line, col, msg, type(self)
29-

Diff for: pandas_style_guide/_ast_helpers.py

+15-23
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,34 @@
11
import ast
22
import warnings
3-
from typing import Container
4-
from typing import Dict
5-
from typing import Set
6-
from typing import Union
7-
3+
from typing import Container, Dict, Set
84

95

106
def ast_parse(contents_text: str) -> ast.Module:
117
# intentionally ignore warnings, we might be fixing warning-ridden syntax
128
with warnings.catch_warnings():
13-
warnings.simplefilter('ignore')
9+
warnings.simplefilter("ignore")
1410
return ast.parse(contents_text.encode())
1511

1612

17-
18-
1913
def is_name_attr(
20-
node: ast.AST,
21-
imports: Dict[str, Set[str]],
22-
mod: str,
23-
names: Container[str],
14+
node: ast.AST,
15+
imports: Dict[str, Set[str]],
16+
mod: str,
17+
names: Container[str],
2418
) -> bool:
2519
return (
26-
isinstance(node, ast.Name) and
27-
node.id in names and
28-
node.id in imports[mod]
20+
isinstance(node, ast.Name)
21+
and node.id in names
22+
and node.id in imports[mod]
2923
) or (
30-
isinstance(node, ast.Attribute) and
31-
isinstance(node.value, ast.Name) and
32-
node.value.id == mod and
33-
node.attr in names
24+
isinstance(node, ast.Attribute)
25+
and isinstance(node.value, ast.Name)
26+
and node.value.id == mod
27+
and node.attr in names
3428
)
3529

3630

3731
def has_starargs(call: ast.Call) -> bool:
38-
return (
39-
any(k.arg is None for k in call.keywords) or
40-
any(isinstance(a, ast.Starred) for a in call.args)
32+
return any(k.arg is None for k in call.keywords) or any(
33+
isinstance(a, ast.Starred) for a in call.args
4134
)
42-

Diff for: pandas_style_guide/_data.py

+39-14
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,72 @@
1-
import pkgutil
2-
import collections
3-
from typing import List, Tuple, Dict, NamedTuple, Set
41
import ast
2+
import collections
3+
import pkgutil
4+
from typing import (
5+
Dict,
6+
Iterator,
7+
List,
8+
NamedTuple,
9+
Protocol,
10+
Sequence,
11+
Set,
12+
Tuple,
13+
Type,
14+
TypeVar,
15+
)
16+
517
from pandas_style_guide import _plugins
618

719
FUNCS = collections.defaultdict(list)
20+
AST_T = TypeVar("AST_T", bound=ast.AST)
21+
22+
23+
class ASTCallbackMapping(Protocol):
24+
def __getitem__(self, tp: Type[AST_T]) -> Tuple[int, int, str]:
25+
...
26+
27+
828
class State(NamedTuple):
929
from_imports: Dict[str, Set[str]]
1030
in_annotation: bool = False
1131

32+
1233
def register(tp):
1334
def register_decorator(func):
1435
FUNCS[tp].append(func)
1536
return func
37+
1638
return register_decorator
1739

40+
1841
def _get_alias(name):
1942
if name.asname is not None:
2043
return name.asname
2144
else:
2245
return name.name
2346

24-
def visit(funcs, tree: ast.Module) -> Dict[int, List[int]]:
47+
48+
def visit(
49+
funcs: ASTCallbackMapping,
50+
tree: ast.Module,
51+
) -> Iterator[Tuple[str, str, int]]:
2552
"Step through tree, recording when nodes are in annotations."
2653
initial_state = State(
2754
from_imports=collections.defaultdict(set),
2855
)
29-
nodes: List[Tuple[bool, ast.AST, ast.AST]] = [(initial_state, tree, tree)]
56+
nodes: List[Tuple[State, ast.AST, ast.AST]] = [(initial_state, tree, tree)]
3057

3158
while nodes:
3259
state, node, parent = nodes.pop()
3360
tp = type(node)
3461
for ast_func in funcs[tp]:
3562
yield from ast_func(state, node, parent)
3663

37-
if (
38-
isinstance(node, ast.ImportFrom)
39-
and node.module is not None
40-
):
41-
state.from_imports[node.module.split('.')[0]].update(
64+
if isinstance(node, ast.ImportFrom) and node.module is not None:
65+
state.from_imports[node.module.split(".")[0]].update(
4266
_get_alias(name) for name in node.names if not name.asname
4367
)
4468
elif isinstance(node, ast.Import):
45-
for name in node.names:
69+
for name in node.names:
4670
state.from_imports[_get_alias(name)]
4771

4872
for name in reversed(node._fields):
@@ -58,12 +82,13 @@ def visit(funcs, tree: ast.Module) -> Dict[int, List[int]]:
5882
if isinstance(value, ast.AST):
5983
nodes.append((next_state, value, node))
6084

85+
6186
def _import_plugins() -> None:
6287
# https://github.com/python/mypy/issues/1422
6388
plugins_path: str = _plugins.__path__ # type: ignore
64-
mod_infos = pkgutil.walk_packages(plugins_path, f'{_plugins.__name__}.')
89+
mod_infos = pkgutil.walk_packages(plugins_path, f"{_plugins.__name__}.")
6590
for _, name, _ in mod_infos:
66-
__import__(name, fromlist=['_trash'])
91+
__import__(name, fromlist=["_trash"])
6792

6893

69-
_import_plugins()
94+
_import_plugins()

Diff for: pandas_style_guide/_plugins/abc.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import ast
2-
from pandas_style_guide._data import register
3-
from pandas_style_guide._ast_helpers import is_name_attr
2+
from typing import Iterator, Tuple
3+
4+
from pandas_style_guide._data import State, register
5+
6+
MSG = "PSG010 don't import from collections.abc"
7+
48

5-
MSG = 'PSG010 don\'t import from collections.abc'
69
@register(ast.ImportFrom)
7-
def abc(state, node, parent):
8-
if node.module == 'collections.abc' or (node.module == 'collections' and 'abc' in {name.name for name in node.names}):
9-
yield node.lineno, node.col_offset, MSG
10+
def visit_ImportFrom(
11+
state: State,
12+
node: ast.ImportFrom,
13+
parent: ast.AST,
14+
) -> Iterator[Tuple[int, int, str]]:
15+
if node.module == "collections.abc" or (
16+
node.module == "collections"
17+
and "abc" in {name.name for name in node.names}
18+
):
19+
yield node.lineno, node.col_offset, MSG

Diff for: pandas_style_guide/_plugins/api_types.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import ast
2-
from pandas_style_guide._data import register
2+
from typing import Iterator, Tuple
3+
4+
from pandas_style_guide._data import State, register
5+
6+
MSG = "PSG005 dont use pd.api.types, import from pandas.api.types instead"
7+
38

4-
MSG = 'PSG005 dont use pd.api.types, import from pandas.api.types instead'
59
@register(ast.Attribute)
6-
def check_for_pytest_warns(state, node, parent):
7-
if isinstance(node.value, ast.Attribute) and node.value.attr == 'types' and isinstance(node.value.value, ast.Attribute) and node.value.value.attr == 'api' and isinstance(node.value.value.value, ast.Name) and node.value.value.value.id in {'pd', 'pandas'}:
10+
def check_for_pytest_warns(
11+
state: State,
12+
node: ast.Attribute,
13+
parent: ast.AST,
14+
) -> Iterator[Tuple[int, int, str]]:
15+
if (
16+
isinstance(node.value, ast.Attribute)
17+
and node.value.attr == "types"
18+
and isinstance(node.value.value, ast.Attribute)
19+
and node.value.value.attr == "api"
20+
and isinstance(node.value.value.value, ast.Name)
21+
and node.value.value.value.id in {"pd", "pandas"}
22+
):
823
yield node.lineno, node.col_offset, MSG

Diff for: pandas_style_guide/_plugins/bare_pytest_raises.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import ast
2-
from pandas_style_guide._data import register
3-
from pandas_style_guide._ast_helpers import is_name_attr
2+
from typing import Iterator, Tuple
3+
4+
from pandas_style_guide._data import State, register
5+
6+
MSG = "PSG010 bare pytest raises found"
7+
48

5-
MSG = 'PSG010 bare pytest raises found'
69
@register(ast.Call)
7-
def np_bool_object(state, node, parent):
10+
def visit_Call(
11+
state: State,
12+
node: ast.Call,
13+
parent: ast.AST,
14+
) -> Iterator[Tuple[int, int, str]]:
815
if not node.keywords:
916
yield node.lineno, node.col_offset, MSG
10-
elif 'match' not in {keyword.arg for keyword in node.keywords}:
17+
elif "match" not in {keyword.arg for keyword in node.keywords}:
1118
yield node.lineno, node.col_offset, MSG

Diff for: pandas_style_guide/_plugins/builtin_filter.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import ast
2-
from pandas_style_guide._data import register
2+
from typing import Iterator, Tuple
3+
4+
from pandas_style_guide._data import State, register
5+
6+
MSG = "PSG004 do not use builtin filter function"
7+
38

4-
MSG = 'PSG004 do not use builtin filter function'
59
@register(ast.Call)
6-
def builtin_filter(state, node, parent):
10+
def visit_Call(
11+
state: State,
12+
node: ast.Call,
13+
parent: ast.AST,
14+
) -> Iterator[Tuple[int, int, str]]:
715
if isinstance(node.func, ast.Name):
8-
if node.func.id == 'filter':
16+
if node.func.id == "filter":
917
yield (node.lineno, node.col_offset, MSG)
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,33 @@
11
import ast
2-
from pandas_style_guide._data import register
2+
from typing import Iterator, Tuple
3+
34
from pandas_style_guide._ast_helpers import is_name_attr
5+
from pandas_style_guide._data import State, register
6+
7+
MSG = "PSG003 Do not use pytest.raises without context manager"
8+
49

5-
MSG = 'PSG003 Do not use pytest.raises without context manager'
610
@register(ast.Call)
7-
def check_contextless_pytest_raises(state, node, parent):
8-
if is_name_attr(node.func, state.from_imports, 'pytest', ('raises', )) and not isinstance(parent, ast.withitem):
11+
def visit_Call(
12+
state: State,
13+
node: ast.Call,
14+
parent: ast.AST,
15+
) -> Iterator[Tuple[int, int, str]]:
16+
if (
17+
is_name_attr(
18+
node.func,
19+
state.from_imports,
20+
"pytest",
21+
("raises",),
22+
)
23+
and not isinstance(parent, ast.withitem)
24+
):
925
yield node.lineno, node.col_offset, MSG
10-
elif isinstance(node.func, ast.Attribute) and node.func.attr == 'raises' and isinstance(node.func.value, ast.Name) and node.func.value.id == 'pytest' and not isinstance(parent, ast.withitem):
26+
elif (
27+
isinstance(node.func, ast.Attribute)
28+
and node.func.attr == "raises"
29+
and isinstance(node.func.value, ast.Name)
30+
and node.func.value.id == "pytest"
31+
and not isinstance(parent, ast.withitem)
32+
):
1133
yield node.lineno, node.col_offset, MSG

Diff for: pandas_style_guide/_plugins/check_for_exec.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import ast
2-
from pandas_style_guide._data import register
2+
from typing import Iterator, Tuple
3+
4+
from pandas_style_guide._data import State, register
5+
6+
MSG = "PSG001 do not use exec"
7+
38

4-
MSG = 'PSG001 do not use exec'
59
@register(ast.Call)
6-
def check_for_exec(state, node, parent):
10+
def visit_Call(
11+
state: State,
12+
node: ast.Call,
13+
parent: ast.AST,
14+
) -> Iterator[Tuple[int, int, str]]:
715
if isinstance(node.func, ast.Name):
8-
if node.func.id == 'exec':
16+
if node.func.id == "exec":
917
yield (node.lineno, node.col_offset, MSG)
+17-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
import ast
2-
from pandas_style_guide._data import register
2+
from typing import Iterator, Tuple
3+
4+
from pandas_style_guide._data import State, register
5+
6+
MSG = "PSG002 do not use pytest.warns"
7+
38

4-
MSG = 'PSG002 do not use pytest.warns'
59
@register(ast.Attribute)
6-
def check_for_pytest_warns(state, node, parent):
7-
if node.attr == 'warns' and isinstance(node.value, ast.Name) and node.value.id == 'pytest' :
8-
yield node.lineno, node.col_offset, MSG
10+
def check_for_pytest_warns(
11+
state: State,
12+
node: ast.Attribute,
13+
parent: ast.AST,
14+
) -> Iterator[Tuple[int, int, str]]:
15+
if (
16+
node.attr == "warns"
17+
and isinstance(node.value, ast.Name)
18+
and node.value.id == "pytest"
19+
):
20+
yield node.lineno, node.col_offset, MSG

Diff for: pandas_style_guide/_plugins/class.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import ast
2-
from pandas_style_guide._data import register
3-
from pandas_style_guide._ast_helpers import is_name_attr
2+
from typing import Iterator, Tuple
3+
4+
from pandas_style_guide._data import State, register
5+
6+
MSG = "PSG008 don't use .__class__, use type()"
7+
48

5-
MSG = 'PSG008 don\'t use .__class__, use type()'
69
@register(ast.Attribute)
7-
def dunder_class(state, node, parent):
8-
if node.attr == '__class__':
9-
yield node.lineno, node.col_offset, MSG
10+
def visit_Attribute(
11+
state: State,
12+
node: ast.Attribute,
13+
parent: ast.AST,
14+
) -> Iterator[Tuple[int, int, str]]:
15+
if node.attr == "__class__":
16+
yield node.lineno, node.col_offset, MSG

0 commit comments

Comments
 (0)