diff --git a/ci/code_checks.sh b/ci/code_checks.sh index f7a513ca22d53..be2a7fa8dbe26 100755 --- a/ci/code_checks.sh +++ b/ci/code_checks.sh @@ -116,6 +116,21 @@ if [[ -z "$CHECK" || "$CHECK" == "lint" ]]; then fi RET=$(($RET + $?)) ; echo $MSG "DONE" + MSG='Check for use of private import across modules' ; echo $MSG + if [[ "$GITHUB_ACTIONS" == "true" ]]; then + $BASE_DIR/scripts/validate_unwanted_patterns.py --validation-type="private_import_across_module" --format="##[error]{source_path}:{line_number}:{msg}" pandas/core/ + RET=$(($RET + $?)) ; echo $MSG "DONE" + + $BASE_DIR/scripts/validate_unwanted_patterns.py --validation-type="private_import_across_module" --format="##[error]{source_path}:{line_number}:{msg}" pandas/tseries/ + RET=$(($RET + $?)) ; echo $MSG "DONE" + else + $BASE_DIR/scripts/validate_unwanted_patterns.py --validation-type="private_import_across_module" pandas/core/ + RET=$(($RET + $?)) ; echo $MSG "DONE" + + $BASE_DIR/scripts/validate_unwanted_patterns.py --validation-type="private_import_across_module" pandas/tseries/ + RET=$(($RET + $?)) ; echo $MSG "DONE" + fi + echo "isort --version-number" isort --version-number diff --git a/scripts/validate_unwanted_patterns.py b/scripts/validate_unwanted_patterns.py index 193fef026a96b..554d9b29d69fd 100755 --- a/scripts/validate_unwanted_patterns.py +++ b/scripts/validate_unwanted_patterns.py @@ -16,10 +16,27 @@ import sys import token import tokenize -from typing import IO, Callable, FrozenSet, Iterable, List, Tuple +from typing import IO, Callable, FrozenSet, Iterable, List, Set, Tuple PATHS_TO_IGNORE: Tuple[str, ...] = ("asv_bench/env",) +PRIVATE_IMPORTS_TO_IGNORE: Set[str] = { + "_extension_array_shared_docs", + "_index_shared_docs", + "_merge_doc", + "_shared_docs", + "_new_Index", + "_new_PeriodIndex", + "_doc_template", + "_interval_shared_docs", + "_apply_docs", + "_arith_doc_FRAME", + "_flex_comp_doc_FRAME", + "_make_flex_doc", + "_op_descriptions", + "_pipe_template", +} + def _get_literal_string_prefix_len(token_string: str) -> int: """ @@ -114,6 +131,38 @@ def bare_pytest_raises(file_obj: IO[str]) -> Iterable[Tuple[int, str]]: ) +def private_import_across_module(file_obj: IO[str]) -> Iterable[Tuple[int, str]]: + """ + Checking that a private function is not imported across modules. + + Parameters + ---------- + file_obj : IO + File-like object containing the Python code to validate. + + Yields + ------ + line_number : int + Line number of import statement, that imports the private function. + msg : str + Explenation of the error. + """ + contents = file_obj.read() + tree = ast.parse(contents) + + for node in ast.walk(tree): + if not (isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom)): + continue + + for module in node.names: + module_name = module.name.split(".")[-1] + if module_name in PRIVATE_IMPORTS_TO_IGNORE: + continue + + if module_name.startswith("_"): + yield (node.lineno, f"Import of internal function {repr(module_name)}") + + def strings_to_concatenate(file_obj: IO[str]) -> Iterable[Tuple[int, str]]: """ This test case is necessary after 'Black' (https://github.com/psf/black), @@ -362,6 +411,7 @@ def main( if __name__ == "__main__": available_validation_types: List[str] = [ "bare_pytest_raises", + "private_import_across_module", "strings_to_concatenate", "strings_with_wrong_placed_whitespace", ]