22Check that test suite file doesn't use the pandas namespace inconsistently.
33
44We check for cases of ``Series`` and ``pd.Series`` appearing in the same file
5- (likewise for some other common classes ).
5+ (likewise for other pandas objects ).
66
77This is meant to be run as a pre-commit hook - to run it manually, you can do:
88
1515though note that you may need to manually fixup some imports and that you will also
1616need the additional dependency `tokenize-rt` (which is left out from the pre-commit
1717hook so that it uses the same virtualenv as the other local ones).
18+
19+ The general structure is similar to that of some plugins from
20+ https://github.com/asottile/pyupgrade .
1821"""
1922
2023import argparse
2124import ast
25+ import sys
2226from typing import (
2327 MutableMapping ,
28+ NamedTuple ,
2429 Optional ,
2530 Sequence ,
2631 Set ,
27- Tuple ,
2832)
2933
30- ERROR_MESSAGE = "Found both `pd.{name}` and `{name}` in {path}"
31- EXCLUDE = {
32- "eval" , # built-in, different from `pd.eval`
33- "np" , # pd.np is deprecated but still tested
34- }
35- Offset = Tuple [int , int ]
34+ ERROR_MESSAGE = (
35+ "{path}:{lineno}:{col_offset}: "
36+ "Found both '{prefix}.{name}' and '{name}' in {path}"
37+ )
38+
39+
40+ class OffsetWithNamespace (NamedTuple ):
41+ lineno : int
42+ col_offset : int
43+ namespace : str
3644
3745
3846class Visitor (ast .NodeVisitor ):
3947 def __init__ (self ) -> None :
40- self .pandas_namespace : MutableMapping [Offset , str ] = {}
41- self .no_namespace : Set [str ] = set ()
48+ self .pandas_namespace : MutableMapping [OffsetWithNamespace , str ] = {}
49+ self .imported_from_pandas : Set [str ] = set ()
4250
4351 def visit_Attribute (self , node : ast .Attribute ) -> None :
44- if (
45- isinstance (node .value , ast .Name )
46- and node .value .id == "pd"
47- and node .attr not in EXCLUDE
48- ):
49- self .pandas_namespace [(node .lineno , node .col_offset )] = node .attr
52+ if isinstance (node .value , ast .Name ) and node .value .id in {"pandas" , "pd" }:
53+ offset_with_namespace = OffsetWithNamespace (
54+ node .lineno , node .col_offset , node .value .id
55+ )
56+ self .pandas_namespace [offset_with_namespace ] = node .attr
5057 self .generic_visit (node )
5158
52- def visit_Name (self , node : ast .Name ) -> None :
53- if node .id not in EXCLUDE :
54- self .no_namespace . add ( node .id )
59+ def visit_ImportFrom (self , node : ast .ImportFrom ) -> None :
60+ if node .module is not None and "pandas" in node . module :
61+ self .imported_from_pandas . update ( name . name for name in node .names )
5562 self .generic_visit (node )
5663
5764
@@ -64,9 +71,11 @@ def replace_inconsistent_pandas_namespace(visitor: Visitor, content: str) -> str
6471
6572 tokens = src_to_tokens (content )
6673 for n , i in reversed_enumerate (tokens ):
74+ offset_with_namespace = OffsetWithNamespace (i .offset [0 ], i .offset [1 ], i .src )
6775 if (
68- i .offset in visitor .pandas_namespace
69- and visitor .pandas_namespace [i .offset ] in visitor .no_namespace
76+ offset_with_namespace in visitor .pandas_namespace
77+ and visitor .pandas_namespace [offset_with_namespace ]
78+ in visitor .imported_from_pandas
7079 ):
7180 # Replace `pd`
7281 tokens [n ] = i ._replace (src = "" )
@@ -85,16 +94,28 @@ def check_for_inconsistent_pandas_namespace(
8594 visitor = Visitor ()
8695 visitor .visit (tree )
8796
88- inconsistencies = visitor .no_namespace .intersection (
97+ inconsistencies = visitor .imported_from_pandas .intersection (
8998 visitor .pandas_namespace .values ()
9099 )
100+
91101 if not inconsistencies :
92102 # No inconsistent namespace usage, nothing to replace.
93- return content
103+ return None
94104
95105 if not replace :
96- msg = ERROR_MESSAGE .format (name = inconsistencies .pop (), path = path )
97- raise RuntimeError (msg )
106+ inconsistency = inconsistencies .pop ()
107+ lineno , col_offset , prefix = next (
108+ key for key , val in visitor .pandas_namespace .items () if val == inconsistency
109+ )
110+ msg = ERROR_MESSAGE .format (
111+ lineno = lineno ,
112+ col_offset = col_offset ,
113+ prefix = prefix ,
114+ name = inconsistency ,
115+ path = path ,
116+ )
117+ sys .stdout .write (msg )
118+ sys .exit (1 )
98119
99120 return replace_inconsistent_pandas_namespace (visitor , content )
100121
0 commit comments