diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9424b2f34eaff..5b11490479088 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,10 @@ repos: rev: 3.9.0 hooks: - id: flake8 - additional_dependencies: [flake8-comprehensions>=3.1.0, flake8-bugbear>=21.3.2] + additional_dependencies: + - flake8-comprehensions==3.1.0 + - flake8-bugbear==21.3.2 + - pandas-dev-flaker==0.2.0 - id: flake8 name: flake8 (cython) types: [cython] @@ -71,7 +74,11 @@ repos: rev: v1.2.2 hooks: - id: yesqa - additional_dependencies: [flake8==3.9.0] + additional_dependencies: + - flake8==3.9.0 + - flake8-comprehensions==3.1.0 + - flake8-bugbear==21.3.2 + - pandas-dev-flaker==0.2.0 - repo: local hooks: - id: flake8-rst @@ -82,28 +89,6 @@ repos: types: [rst] args: [--filename=*.rst] additional_dependencies: [flake8-rst==0.7.0, flake8==3.7.9] - - id: frame-or-series-union - name: Check for use of Union[Series, DataFrame] instead of FrameOrSeriesUnion alias - entry: Union\[.*(Series,.*DataFrame|DataFrame,.*Series).*\] - language: pygrep - types: [python] - exclude: ^pandas/_typing\.py$ - - id: inconsistent-namespace-usage - name: 'Check for inconsistent use of pandas namespace' - entry: python scripts/check_for_inconsistent_pandas_namespace.py - language: python - types: [python] - - id: no-os-remove - name: Check code for instances of os.remove - entry: os\.remove - language: pygrep - types: [python] - files: ^pandas/tests/ - exclude: | - (?x)^ - pandas/tests/io/excel/test_writers\.py - |pandas/tests/io/pytables/common\.py - |pandas/tests/io/pytables/test_store\.py$ - id: unwanted-patterns name: Unwanted patterns language: pygrep @@ -113,52 +98,10 @@ repos: \#\ type:\ (?!ignore) |\#\ type:\s?ignore(?!\[) - # foo._class__ instead of type(foo) - |\.__class__ - - # np.bool/np.object instead of np.bool_/np.object_ - |np\.bool[^_8] - |np\.object[^_8] - - # imports from pandas.core.common instead of `import pandas.core.common as com` - |from\ pandas\.core\.common\ import - |from\ pandas\.core\ import\ common - - # imports from collections.abc instead of `from collections import abc` - |from\ collections\.abc\ import - - # Numpy - |from\ numpy\ import\ random - |from\ numpy\.random\ import - # Incorrect code-block / IPython directives |\.\.\ code-block\ :: |\.\.\ ipython\ :: types_or: [python, cython, rst] - exclude: ^doc/source/development/code_style\.rst # contains examples of patterns to avoid - - id: unwanted-patterns-in-tests - name: Unwanted patterns in tests - language: pygrep - entry: | - (?x) - # pytest.xfail instead of pytest.mark.xfail - pytest\.xfail - - # imports from pandas._testing instead of `import pandas._testing as tm` - |from\ pandas\._testing\ import - |from\ pandas\ import\ _testing\ as\ tm - - # No direct imports from conftest - |conftest\ import - |import\ conftest - - # pandas.testing instead of tm - |pd\.testing\. - - # pd.api.types instead of from pandas.api.types import ... - |(pd|pandas)\.api\.types\. - files: ^pandas/tests/ - types_or: [python, cython, rst] - id: pip-to-conda name: Generate pip dependency from conda description: This hook checks if the conda environment.yml and requirements-dev.txt are equal @@ -180,35 +123,6 @@ repos: language: python types: [rst] files: ^doc/source/(development|reference)/ - - id: unwanted-patterns-bare-pytest-raises - name: Check for use of bare pytest raises - language: python - entry: python scripts/validate_unwanted_patterns.py --validation-type="bare_pytest_raises" - types: [python] - files: ^pandas/tests/ - exclude: ^pandas/tests/extension/ - - id: unwanted-patterns-private-function-across-module - name: Check for use of private functions across modules - language: python - entry: python scripts/validate_unwanted_patterns.py --validation-type="private_function_across_module" - types: [python] - exclude: ^(asv_bench|pandas/tests|doc)/ - - id: unwanted-patterns-private-import-across-module - name: Check for import of private attributes across modules - language: python - entry: python scripts/validate_unwanted_patterns.py --validation-type="private_import_across_module" - types: [python] - exclude: ^(asv_bench|pandas/tests|doc)/ - - id: unwanted-patterns-strings-to-concatenate - name: Check for use of not concatenated strings - language: python - entry: python scripts/validate_unwanted_patterns.py --validation-type="strings_to_concatenate" - types_or: [python, cython] - - id: unwanted-patterns-strings-with-wrong-placed-whitespace - name: Check for strings with wrong placed spaces - language: python - entry: python scripts/validate_unwanted_patterns.py --validation-type="strings_with_wrong_placed_whitespace" - types_or: [python, cython] - id: use-pd_array-in-core name: Import pandas.array as pd_array in core language: python diff --git a/asv_bench/benchmarks/gil.py b/asv_bench/benchmarks/gil.py index 459046d2decfb..ac7cd87c846d5 100644 --- a/asv_bench/benchmarks/gil.py +++ b/asv_bench/benchmarks/gil.py @@ -31,7 +31,7 @@ except ImportError: from pandas import algos try: - from pandas._testing import test_parallel + from pandas._testing import test_parallel # noqa: PDF014 have_real_test_parallel = True except ImportError: diff --git a/asv_bench/benchmarks/pandas_vb_common.py b/asv_bench/benchmarks/pandas_vb_common.py index 7bd4d639633b3..ed44102700dc6 100644 --- a/asv_bench/benchmarks/pandas_vb_common.py +++ b/asv_bench/benchmarks/pandas_vb_common.py @@ -70,7 +70,7 @@ class BaseIO: def remove(self, f): """Remove created files""" try: - os.remove(f) + os.remove(f) # noqa: PDF008 except OSError: # On Windows, attempting to remove a file that is in use # causes an exception to be raised diff --git a/ci/code_checks.sh b/ci/code_checks.sh index 3b1774ade6f85..d4b6c0d6ff09d 100755 --- a/ci/code_checks.sh +++ b/ci/code_checks.sh @@ -64,27 +64,6 @@ fi ### PATTERNS ### if [[ -z "$CHECK" || "$CHECK" == "patterns" ]]; then - MSG='Check for use of exec' ; echo $MSG - invgrep -R --include="*.py*" -E "[^a-zA-Z0-9_]exec\(" pandas - RET=$(($RET + $?)) ; echo $MSG "DONE" - - MSG='Check for pytest warns' ; echo $MSG - invgrep -r -E --include '*.py' 'pytest\.warns' pandas/tests/ - RET=$(($RET + $?)) ; echo $MSG "DONE" - - MSG='Check for pytest raises without context' ; echo $MSG - invgrep -r -E --include '*.py' "[[:space:]] pytest.raises" pandas/tests/ - RET=$(($RET + $?)) ; echo $MSG "DONE" - - MSG='Check for use of builtin filter function' ; echo $MSG - invgrep -R --include="*.py" -P '(?`_, to +check our codebase for unwanted patterns. See its ``README`` for the up-to-date list of rules we enforce. Testing ======= diff --git a/environment.yml b/environment.yml index 90a9186aa017f..146bf6db08d8b 100644 --- a/environment.yml +++ b/environment.yml @@ -21,8 +21,8 @@ dependencies: - black=20.8b1 - cpplint - flake8=3.9.0 - - flake8-bugbear>=21.3.2 # used by flake8, find likely bugs - - flake8-comprehensions>=3.1.0 # used by flake8, linting of unnecessary comprehensions + - flake8-bugbear=21.3.2 # used by flake8, find likely bugs + - flake8-comprehensions=3.1.0 # used by flake8, linting of unnecessary comprehensions - isort>=5.2.1 # check that imports are in the right order - mypy=0.812 - pre-commit>=2.9.2 @@ -117,3 +117,4 @@ dependencies: - pip: - git+https://github.com/pydata/pydata-sphinx-theme.git@master - numpydoc < 1.2 # 2021-02-09 1.2dev breaking CI + - pandas-dev-flaker==0.2.0 diff --git a/pandas/_testing/__init__.py b/pandas/_testing/__init__.py index a603222094bdb..aaf58f1fcb150 100644 --- a/pandas/_testing/__init__.py +++ b/pandas/_testing/__init__.py @@ -915,7 +915,7 @@ def external_error_raised(expected_exception: type[Exception]) -> ContextManager """ import pytest - return pytest.raises(expected_exception, match=None) + return pytest.raises(expected_exception, match=None) # noqa: PDF010 cython_table = pd.core.common._cython_table.items() diff --git a/pandas/core/window/ewm.py b/pandas/core/window/ewm.py index 67bcdb0a387dd..0628aa5add4a3 100644 --- a/pandas/core/window/ewm.py +++ b/pandas/core/window/ewm.py @@ -21,7 +21,7 @@ from pandas.core.dtypes.common import is_datetime64_ns_dtype from pandas.core.dtypes.missing import isna -import pandas.core.common as common +import pandas.core.common as common # noqa: PDF018 from pandas.core.util.numba_ import maybe_use_numba from pandas.core.window.common import zsqrt from pandas.core.window.doc import ( diff --git a/pandas/core/window/rolling.py b/pandas/core/window/rolling.py index 33b1ceee6e529..e4710254d9311 100644 --- a/pandas/core/window/rolling.py +++ b/pandas/core/window/rolling.py @@ -56,7 +56,7 @@ DataError, SelectionMixin, ) -import pandas.core.common as common +import pandas.core.common as com from pandas.core.indexes.api import ( Index, MultiIndex, @@ -643,7 +643,7 @@ def _apply_pairwise( ) gb_pairs = ( - common.maybe_make_list(pair) for pair in self._grouper.indices.keys() + com.maybe_make_list(pair) for pair in self._grouper.indices.keys() ) groupby_codes = [] groupby_levels = [] diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py index ee90dfa3e9a52..c36552f59da71 100644 --- a/pandas/tests/api/test_api.py +++ b/pandas/tests/api/test_api.py @@ -274,7 +274,7 @@ class TestTesting(Base): ] def test_testing(self): - from pandas import testing + from pandas import testing # noqa: PDF015 self.check(testing, self.funcs) diff --git a/pandas/tests/indexes/object/test_astype.py b/pandas/tests/indexes/object/test_astype.py index 42c7b8eb4aeec..9bfc0c1312200 100644 --- a/pandas/tests/indexes/object/test_astype.py +++ b/pandas/tests/indexes/object/test_astype.py @@ -1,5 +1,5 @@ from pandas import Index -import pandas.testing as tm +import pandas._testing as tm def test_astype_str_from_bytes(): diff --git a/pandas/tests/io/pytables/common.py b/pandas/tests/io/pytables/common.py index 7e7a76e287d32..6a9d5745ab457 100644 --- a/pandas/tests/io/pytables/common.py +++ b/pandas/tests/io/pytables/common.py @@ -16,7 +16,7 @@ def safe_remove(path): if path is not None: try: - os.remove(path) + os.remove(path) # noqa: PDF008 except OSError: pass diff --git a/pandas/tests/io/pytables/test_store.py b/pandas/tests/io/pytables/test_store.py index 24a4d35b5d94d..bb6928d2fd95a 100644 --- a/pandas/tests/io/pytables/test_store.py +++ b/pandas/tests/io/pytables/test_store.py @@ -911,7 +911,7 @@ def do_copy(f, new_f=None, keys=None, propindexes=True, **kwargs): os.close(fd) except (OSError, ValueError): pass - os.remove(new_f) + os.remove(new_f) # noqa: PDF008 # new table df = tm.makeDataFrame() diff --git a/pandas/tests/tools/test_to_numeric.py b/pandas/tests/tools/test_to_numeric.py index 30d6436c7e250..e863fb45b1f81 100644 --- a/pandas/tests/tools/test_to_numeric.py +++ b/pandas/tests/tools/test_to_numeric.py @@ -232,9 +232,7 @@ def test_type_check(errors): # see gh-11776 df = DataFrame({"a": [1, -3.14, 7], "b": ["4", "5", "6"]}) kwargs = {"errors": errors} if errors is not None else {} - error_ctx = pytest.raises(TypeError, match="1-d array") - - with error_ctx: + with pytest.raises(TypeError, match="1-d array"): to_numeric(df, **kwargs) diff --git a/requirements-dev.txt b/requirements-dev.txt index 02a4e63374305..33deeef9f1f82 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,8 +9,8 @@ cython>=0.29.21 black==20.8b1 cpplint flake8==3.9.0 -flake8-bugbear>=21.3.2 -flake8-comprehensions>=3.1.0 +flake8-bugbear==21.3.2 +flake8-comprehensions==3.1.0 isort>=5.2.1 mypy==0.812 pre-commit>=2.9.2 @@ -80,3 +80,4 @@ tabulate>=0.8.3 natsort git+https://github.com/pydata/pydata-sphinx-theme.git@master numpydoc < 1.2 +pandas-dev-flaker==0.2.0 diff --git a/scripts/check_for_inconsistent_pandas_namespace.py b/scripts/check_for_inconsistent_pandas_namespace.py deleted file mode 100644 index 3c21821e794a9..0000000000000 --- a/scripts/check_for_inconsistent_pandas_namespace.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -Check that test suite file doesn't use the pandas namespace inconsistently. - -We check for cases of ``Series`` and ``pd.Series`` appearing in the same file -(likewise for other pandas objects). - -This is meant to be run as a pre-commit hook - to run it manually, you can do: - - pre-commit run inconsistent-namespace-usage --all-files - -To automatically fixup a given file, you can pass `--replace`, e.g. - - python scripts/check_for_inconsistent_pandas_namespace.py test_me.py --replace - -though note that you may need to manually fixup some imports and that you will also -need the additional dependency `tokenize-rt` (which is left out from the pre-commit -hook so that it uses the same virtualenv as the other local ones). - -The general structure is similar to that of some plugins from -https://github.com/asottile/pyupgrade . -""" - -import argparse -import ast -import sys -from typing import ( - MutableMapping, - NamedTuple, - Optional, - Sequence, - Set, -) - -ERROR_MESSAGE = ( - "{path}:{lineno}:{col_offset}: " - "Found both '{prefix}.{name}' and '{name}' in {path}" -) - - -class OffsetWithNamespace(NamedTuple): - lineno: int - col_offset: int - namespace: str - - -class Visitor(ast.NodeVisitor): - def __init__(self) -> None: - self.pandas_namespace: MutableMapping[OffsetWithNamespace, str] = {} - self.imported_from_pandas: Set[str] = set() - - def visit_Attribute(self, node: ast.Attribute) -> None: - if isinstance(node.value, ast.Name) and node.value.id in {"pandas", "pd"}: - offset_with_namespace = OffsetWithNamespace( - node.lineno, node.col_offset, node.value.id - ) - self.pandas_namespace[offset_with_namespace] = node.attr - self.generic_visit(node) - - def visit_ImportFrom(self, node: ast.ImportFrom) -> None: - if node.module is not None and "pandas" in node.module: - self.imported_from_pandas.update(name.name for name in node.names) - self.generic_visit(node) - - -def replace_inconsistent_pandas_namespace(visitor: Visitor, content: str) -> str: - from tokenize_rt import ( - reversed_enumerate, - src_to_tokens, - tokens_to_src, - ) - - tokens = src_to_tokens(content) - for n, i in reversed_enumerate(tokens): - offset_with_namespace = OffsetWithNamespace(i.offset[0], i.offset[1], i.src) - if ( - offset_with_namespace in visitor.pandas_namespace - and visitor.pandas_namespace[offset_with_namespace] - in visitor.imported_from_pandas - ): - # Replace `pd` - tokens[n] = i._replace(src="") - # Replace `.` - tokens[n + 1] = tokens[n + 1]._replace(src="") - - new_src: str = tokens_to_src(tokens) - return new_src - - -def check_for_inconsistent_pandas_namespace( - content: str, path: str, *, replace: bool -) -> Optional[str]: - tree = ast.parse(content) - - visitor = Visitor() - visitor.visit(tree) - - inconsistencies = visitor.imported_from_pandas.intersection( - visitor.pandas_namespace.values() - ) - - if not inconsistencies: - # No inconsistent namespace usage, nothing to replace. - return None - - if not replace: - inconsistency = inconsistencies.pop() - lineno, col_offset, prefix = next( - key for key, val in visitor.pandas_namespace.items() if val == inconsistency - ) - msg = ERROR_MESSAGE.format( - lineno=lineno, - col_offset=col_offset, - prefix=prefix, - name=inconsistency, - path=path, - ) - sys.stdout.write(msg) - sys.exit(1) - - return replace_inconsistent_pandas_namespace(visitor, content) - - -def main(argv: Optional[Sequence[str]] = None) -> None: - parser = argparse.ArgumentParser() - parser.add_argument("paths", nargs="*") - parser.add_argument("--replace", action="store_true") - args = parser.parse_args(argv) - - for path in args.paths: - with open(path, encoding="utf-8") as fd: - content = fd.read() - new_content = check_for_inconsistent_pandas_namespace( - content, path, replace=args.replace - ) - if not args.replace or new_content is None: - continue - with open(path, "w", encoding="utf-8") as fd: - fd.write(new_content) - - -if __name__ == "__main__": - main() diff --git a/scripts/sync_flake8_versions.py b/scripts/sync_flake8_versions.py index 8dd7abcf47f02..cb6bb1eb0986e 100644 --- a/scripts/sync_flake8_versions.py +++ b/scripts/sync_flake8_versions.py @@ -1,5 +1,5 @@ """ -Check that the flake8 pins are the same in: +Check that the flake8 (and pandas-dev-flaker) pins are the same in: - environment.yml - .pre-commit-config.yaml, in the flake8 hook @@ -12,68 +12,152 @@ - ``python scripts/sync_flake8_versions.py``, or - ``pre-commit run sync-flake8-versions --all-files``. """ +from __future__ import annotations + +from dataclasses import ( + dataclass, + replace, +) import sys from typing import ( Any, Mapping, - NamedTuple, Sequence, - Tuple, TypeVar, ) import yaml -class Revisions(NamedTuple): - precommit_rev: str - precommit_yesqa_rev: str - environment_rev: str +@dataclass +class Revision: + name: str + compare: str + version: str + + +@dataclass +class Revisions: + name: str + pre_commit: Revision | None = None + yesqa: Revision | None = None + environment: Revision | None = None YamlMapping = Mapping[str, Any] Repo = TypeVar("Repo", bound=YamlMapping) +COMPARE = ("<=", "==", ">=", "<", ">", "=") -def _get_repo_hook(repos: Sequence[Repo], hook_name: str) -> Tuple[Repo, YamlMapping]: + +def _get_repo_hook(repos: Sequence[Repo], hook_name: str) -> tuple[Repo, YamlMapping]: for repo in repos: for hook in repo["hooks"]: if hook["id"] == hook_name: return repo, hook - else: + else: # pragma: no cover raise RuntimeError(f"Repo with hook {hook_name} not found") -def get_revisions(precommit_config: YamlMapping, environment: YamlMapping) -> Revisions: - repos = precommit_config["repos"] - flake8_repo, _ = _get_repo_hook(repos, "flake8") - precommit_rev = flake8_repo["rev"] - - _, yesqa_hook = _get_repo_hook(repos, "yesqa") - additional_dependencies = yesqa_hook.get("additional_dependencies", []) - for dep in additional_dependencies: - if "==" in dep: - pkg, rev = dep.split("==", maxsplit=1) - if pkg == "flake8": - precommit_yesqa_rev = rev - break +def _conda_to_pip_compat(dep): + if dep.compare == "=": + return replace(dep, compare="==") else: - raise RuntimeError( - "flake8 not found, or not pinned, in additional dependencies of yesqa " - "hook in .pre-commit-config.yaml" + return dep + + +def _validate_additional_dependencies( + flake8_additional_dependencies, + yesqa_additional_dependencies, + environment_additional_dependencies, +) -> None: + for dep in flake8_additional_dependencies: + if dep not in yesqa_additional_dependencies: + sys.stdout.write( + f"Mismatch of '{dep.name}' version between 'flake8' " + "and 'yesqa' in '.pre-commit-config.yaml'\n" + ) + sys.exit(1) + if dep not in environment_additional_dependencies: + sys.stdout.write( + f"Mismatch of '{dep.name}' version between 'enviroment.yml' " + "and additional dependencies of 'flake8' in '.pre-commit-config.yaml'\n" + ) + sys.exit(1) + + +def _validate_revisions(revisions): + if revisions.environment != revisions.pre_commit: + sys.stdout.write( + f"{revisions.name} in 'environment.yml' does not " + "match in 'flake8' from 'pre-commit'\n" ) + sys.exit(1) + + if revisions.yesqa != revisions.pre_commit: + sys.stdout.write( + f"{revisions.name} in 'yesqa' does not match " + "in 'flake8' from 'pre-commit'\n" + ) + sys.exit(1) - deps = environment["dependencies"] + +def _process_dependencies(deps): for dep in deps: - if isinstance(dep, str) and "=" in dep: - pkg, rev = dep.split("=", maxsplit=1) - if pkg == "flake8": - environment_rev = rev - break - else: - raise RuntimeError("flake8 not found, or not pinned, in environment.yml") + if isinstance(dep, str): + for compare in COMPARE: + if compare in dep: + pkg, rev = dep.split(compare, maxsplit=1) + yield _conda_to_pip_compat(Revision(pkg, compare, rev)) + break + else: + yield from _process_dependencies(dep["pip"]) + + +def get_revisions( + precommit_config: YamlMapping, environment: YamlMapping +) -> tuple[Revisions, Revisions]: + flake8_revisions = Revisions(name="flake8") + pandas_dev_flaker_revisions = Revisions(name="pandas-dev-flaker") + + repos = precommit_config["repos"] + flake8_repo, flake8_hook = _get_repo_hook(repos, "flake8") + flake8_revisions.pre_commit = Revision("flake8", "==", flake8_repo["rev"]) + flake8_additional_dependencies = [] + for dep in _process_dependencies(flake8_hook.get("additional_dependencies", [])): + if dep.name == "pandas-dev-flaker": + pandas_dev_flaker_revisions.pre_commit = dep + else: + flake8_additional_dependencies.append(dep) - return Revisions(precommit_rev, precommit_yesqa_rev, environment_rev) + _, yesqa_hook = _get_repo_hook(repos, "yesqa") + yesqa_additional_dependencies = [] + for dep in _process_dependencies(yesqa_hook.get("additional_dependencies", [])): + if dep.name == "flake8": + flake8_revisions.yesqa = dep + elif dep.name == "pandas-dev-flaker": + pandas_dev_flaker_revisions.yesqa = dep + else: + yesqa_additional_dependencies.append(dep) + + environment_dependencies = environment["dependencies"] + environment_additional_dependencies = [] + for dep in _process_dependencies(environment_dependencies): + if dep.name == "flake8": + flake8_revisions.environment = dep + elif dep.name == "pandas-dev-flaker": + pandas_dev_flaker_revisions.environment = dep + else: + environment_additional_dependencies.append(dep) + + _validate_additional_dependencies( + flake8_additional_dependencies, + yesqa_additional_dependencies, + environment_additional_dependencies, + ) + + for revisions in flake8_revisions, pandas_dev_flaker_revisions: + _validate_revisions(revisions) if __name__ == "__main__": @@ -81,21 +165,5 @@ def get_revisions(precommit_config: YamlMapping, environment: YamlMapping) -> Re precommit_config = yaml.safe_load(fd) with open("environment.yml") as fd: environment = yaml.safe_load(fd) - - revisions = get_revisions(precommit_config, environment) - - if revisions.environment_rev != revisions.precommit_rev: - sys.stdout.write( - f"flake8 pin in environment.yml is {revisions.environment_rev}, " - f"should be {revisions.precommit_rev}\n" - ) - sys.exit(1) - - if revisions.precommit_yesqa_rev != revisions.precommit_rev: - sys.stdout.write( - f"flake8 pin in yesqa is {revisions.precommit_yesqa_rev}, " - f"should be {revisions.precommit_rev}\n" - ) - sys.exit(1) - + get_revisions(precommit_config, environment) sys.exit(0) diff --git a/scripts/tests/test_inconsistent_namespace_check.py b/scripts/tests/test_inconsistent_namespace_check.py deleted file mode 100644 index eb995158d8cb4..0000000000000 --- a/scripts/tests/test_inconsistent_namespace_check.py +++ /dev/null @@ -1,61 +0,0 @@ -import pytest - -from ..check_for_inconsistent_pandas_namespace import ( - check_for_inconsistent_pandas_namespace, -) - -BAD_FILE_0 = ( - "from pandas import Categorical\n" - "cat_0 = Categorical()\n" - "cat_1 = pd.Categorical()" -) -BAD_FILE_1 = ( - "from pandas import Categorical\n" - "cat_0 = pd.Categorical()\n" - "cat_1 = Categorical()" -) -BAD_FILE_2 = ( - "from pandas import Categorical\n" - "cat_0 = pandas.Categorical()\n" - "cat_1 = Categorical()" -) -GOOD_FILE_0 = ( - "from pandas import Categorical\ncat_0 = Categorical()\ncat_1 = Categorical()" -) -GOOD_FILE_1 = "cat_0 = pd.Categorical()\ncat_1 = pd.Categorical()" -GOOD_FILE_2 = "from array import array\nimport pandas as pd\narr = pd.array([])" -PATH = "t.py" - - -@pytest.mark.parametrize( - "content, expected", - [ - (BAD_FILE_0, "t.py:3:8: Found both 'pd.Categorical' and 'Categorical' in t.py"), - (BAD_FILE_1, "t.py:2:8: Found both 'pd.Categorical' and 'Categorical' in t.py"), - ( - BAD_FILE_2, - "t.py:2:8: Found both 'pandas.Categorical' and 'Categorical' in t.py", - ), - ], -) -def test_inconsistent_usage(content, expected, capsys): - with pytest.raises(SystemExit): - check_for_inconsistent_pandas_namespace(content, PATH, replace=False) - result, _ = capsys.readouterr() - assert result == expected - - -@pytest.mark.parametrize("content", [GOOD_FILE_0, GOOD_FILE_1, GOOD_FILE_2]) -@pytest.mark.parametrize("replace", [True, False]) -def test_consistent_usage(content, replace): - # should not raise - check_for_inconsistent_pandas_namespace(content, PATH, replace=replace) - - -@pytest.mark.parametrize("content", [BAD_FILE_0, BAD_FILE_1, BAD_FILE_2]) -def test_inconsistent_usage_with_replace(content): - result = check_for_inconsistent_pandas_namespace(content, PATH, replace=True) - expected = ( - "from pandas import Categorical\ncat_0 = Categorical()\ncat_1 = Categorical()" - ) - assert result == expected diff --git a/scripts/tests/test_sync_flake8_versions.py b/scripts/tests/test_sync_flake8_versions.py index fc559f3e5e982..d9b6dbe8c3f0a 100644 --- a/scripts/tests/test_sync_flake8_versions.py +++ b/scripts/tests/test_sync_flake8_versions.py @@ -1,25 +1,221 @@ -from ..sync_flake8_versions import ( - Revisions, - get_revisions, -) +import pytest +from ..sync_flake8_versions import get_revisions -def test_get_revisions(): + +def test_wrong_yesqa_flake8(capsys): + precommit_config = { + "repos": [ + { + "repo": "https://gitlab.com/pycqa/flake8", + "rev": "0.1.1", + "hooks": [ + { + "id": "flake8", + } + ], + }, + { + "repo": "https://github.com/asottile/yesqa", + "rev": "v1.2.2", + "hooks": [ + { + "id": "yesqa", + "additional_dependencies": [ + "flake8==0.4.2", + ], + } + ], + }, + ] + } + environment = { + "dependencies": [ + "flake8=0.1.1", + ] + } + with pytest.raises(SystemExit, match=None): + get_revisions(precommit_config, environment) + result, _ = capsys.readouterr() + expected = "flake8 in 'yesqa' does not match in 'flake8' from 'pre-commit'\n" + assert result == expected + + +def test_wrong_env_flake8(capsys): + precommit_config = { + "repos": [ + { + "repo": "https://gitlab.com/pycqa/flake8", + "rev": "0.1.1", + "hooks": [ + { + "id": "flake8", + } + ], + }, + { + "repo": "https://github.com/asottile/yesqa", + "rev": "v1.2.2", + "hooks": [ + { + "id": "yesqa", + "additional_dependencies": [ + "flake8==0.4.2", + ], + } + ], + }, + ] + } + environment = { + "dependencies": [ + "flake8=1.5.6", + ] + } + with pytest.raises(SystemExit, match=None): + get_revisions(precommit_config, environment) + result, _ = capsys.readouterr() + expected = ( + "flake8 in 'environment.yml' does not match in 'flake8' from 'pre-commit'\n" + ) + assert result == expected + + +def test_wrong_yesqa_add_dep(capsys): + precommit_config = { + "repos": [ + { + "repo": "https://gitlab.com/pycqa/flake8", + "rev": "0.1.1", + "hooks": [ + { + "id": "flake8", + "additional_dependencies": [ + "flake8-bugs==1.1.1", + ], + } + ], + }, + { + "repo": "https://github.com/asottile/yesqa", + "rev": "v1.2.2", + "hooks": [ + { + "id": "yesqa", + "additional_dependencies": [ + "flake8==0.4.2", + "flake8-bugs>=1.1.1", + ], + } + ], + }, + ] + } + environment = { + "dependencies": [ + "flake8=1.5.6", + "flake8-bugs=1.1.1", + ] + } + with pytest.raises(SystemExit, match=None): + get_revisions(precommit_config, environment) + result, _ = capsys.readouterr() + expected = ( + "Mismatch of 'flake8-bugs' version between 'flake8' and 'yesqa' in " + "'.pre-commit-config.yaml'\n" + ) + assert result == expected + + +def test_wrong_env_add_dep(capsys): precommit_config = { "repos": [ { "repo": "https://gitlab.com/pycqa/flake8", - "rev": "foo", - "hooks": [{"id": "flake8"}], + "rev": "0.1.1", + "hooks": [ + { + "id": "flake8", + "additional_dependencies": [ + "flake8-bugs==1.1.1", + ], + } + ], }, { "repo": "https://github.com/asottile/yesqa", "rev": "v1.2.2", - "hooks": [{"id": "yesqa", "additional_dependencies": ["flake8==bar"]}], + "hooks": [ + { + "id": "yesqa", + "additional_dependencies": [ + "flake8==0.4.2", + "flake8-bugs==1.1.1", + ], + } + ], }, ] } - environment = {"dependencies": ["flake8=qux"]} - result = get_revisions(precommit_config, environment) - expected = Revisions("foo", "bar", "qux") + environment = { + "dependencies": [ + "flake8=1.5.6", + "flake8-bugs=1.1.2", + ] + } + with pytest.raises(SystemExit, match=None): + get_revisions(precommit_config, environment) + result, _ = capsys.readouterr() + expected = ( + "Mismatch of 'flake8-bugs' version between 'enviroment.yml' " + "and additional dependencies of 'flake8' in '.pre-commit-config.yaml'\n" + ) assert result == expected + + +def test_get_revisions_no_failure(capsys): + precommit_config = { + "repos": [ + { + "repo": "https://gitlab.com/pycqa/flake8", + "rev": "0.1.1", + "hooks": [ + { + "id": "flake8", + "additional_dependencies": [ + "pandas-dev-flaker==0.2.0", + "flake8-bugs==1.1.1", + ], + } + ], + }, + { + "repo": "https://github.com/asottile/yesqa", + "rev": "v1.2.2", + "hooks": [ + { + "id": "yesqa", + "additional_dependencies": [ + "flake8==0.1.1", + "pandas-dev-flaker==0.2.0", + "flake8-bugs==1.1.1", + ], + } + ], + }, + ] + } + environment = { + "dependencies": [ + "flake8=0.1.1", + "flake8-bugs=1.1.1", + { + "pip": [ + "git+https://github.com/pydata/pydata-sphinx-theme.git@master", + "pandas-dev-flaker==0.2.0", + ] + }, + ] + } + # should not raise + get_revisions(precommit_config, environment) diff --git a/scripts/tests/test_use_pd_array_in_core.py b/scripts/tests/test_use_pd_array_in_core.py index 9c66199a82846..8f13a6e735899 100644 --- a/scripts/tests/test_use_pd_array_in_core.py +++ b/scripts/tests/test_use_pd_array_in_core.py @@ -14,7 +14,7 @@ def test_inconsistent_usage(content, capsys): result_msg = ( "t.py:2:0: Don't use pd.array in core, import array as pd_array instead\n" ) - with pytest.raises(SystemExit): + with pytest.raises(SystemExit, match=None): use_pd_array(content, PATH) expected_msg, _ = capsys.readouterr() assert result_msg == expected_msg diff --git a/scripts/tests/test_validate_unwanted_patterns.py b/scripts/tests/test_validate_unwanted_patterns.py deleted file mode 100644 index ef93fd1d21981..0000000000000 --- a/scripts/tests/test_validate_unwanted_patterns.py +++ /dev/null @@ -1,419 +0,0 @@ -import io - -import pytest - -from .. import validate_unwanted_patterns - - -class TestBarePytestRaises: - @pytest.mark.parametrize( - "data", - [ - ( - """ - with pytest.raises(ValueError, match="foo"): - pass - """ - ), - ( - """ - # with pytest.raises(ValueError, match="foo"): - # pass - """ - ), - ( - """ - # with pytest.raises(ValueError): - # pass - """ - ), - ( - """ - with pytest.raises( - ValueError, - match="foo" - ): - pass - """ - ), - ], - ) - def test_pytest_raises(self, data): - fd = io.StringIO(data.strip()) - result = list(validate_unwanted_patterns.bare_pytest_raises(fd)) - assert result == [] - - @pytest.mark.parametrize( - "data, expected", - [ - ( - ( - """ - with pytest.raises(ValueError): - pass - """ - ), - [ - ( - 1, - ( - "Bare pytests raise have been found. " - "Please pass in the argument 'match' " - "as well the exception." - ), - ), - ], - ), - ( - ( - """ - with pytest.raises(ValueError, match="foo"): - with pytest.raises(ValueError): - pass - pass - """ - ), - [ - ( - 2, - ( - "Bare pytests raise have been found. " - "Please pass in the argument 'match' " - "as well the exception." - ), - ), - ], - ), - ( - ( - """ - with pytest.raises(ValueError): - with pytest.raises(ValueError, match="foo"): - pass - pass - """ - ), - [ - ( - 1, - ( - "Bare pytests raise have been found. " - "Please pass in the argument 'match' " - "as well the exception." - ), - ), - ], - ), - ( - ( - """ - with pytest.raises( - ValueError - ): - pass - """ - ), - [ - ( - 1, - ( - "Bare pytests raise have been found. " - "Please pass in the argument 'match' " - "as well the exception." - ), - ), - ], - ), - ( - ( - """ - with pytest.raises( - ValueError, - # match = "foo" - ): - pass - """ - ), - [ - ( - 1, - ( - "Bare pytests raise have been found. " - "Please pass in the argument 'match' " - "as well the exception." - ), - ), - ], - ), - ], - ) - def test_pytest_raises_raises(self, data, expected): - fd = io.StringIO(data.strip()) - result = list(validate_unwanted_patterns.bare_pytest_raises(fd)) - assert result == expected - - -@pytest.mark.parametrize( - "data, expected", - [ - ( - 'msg = ("bar " "baz")', - [ - ( - 1, - ( - "String unnecessarily split in two by black. " - "Please merge them manually." - ), - ) - ], - ), - ( - 'msg = ("foo " "bar " "baz")', - [ - ( - 1, - ( - "String unnecessarily split in two by black. " - "Please merge them manually." - ), - ), - ( - 1, - ( - "String unnecessarily split in two by black. " - "Please merge them manually." - ), - ), - ], - ), - ], -) -def test_strings_to_concatenate(data, expected): - fd = io.StringIO(data.strip()) - result = list(validate_unwanted_patterns.strings_to_concatenate(fd)) - assert result == expected - - -class TestStringsWithWrongPlacedWhitespace: - @pytest.mark.parametrize( - "data", - [ - ( - """ - msg = ( - "foo\n" - " bar" - ) - """ - ), - ( - """ - msg = ( - "foo" - " bar" - "baz" - ) - """ - ), - ( - """ - msg = ( - f"foo" - " bar" - ) - """ - ), - ( - """ - msg = ( - "foo" - f" bar" - ) - """ - ), - ( - """ - msg = ( - "foo" - rf" bar" - ) - """ - ), - ], - ) - def test_strings_with_wrong_placed_whitespace(self, data): - fd = io.StringIO(data.strip()) - result = list( - validate_unwanted_patterns.strings_with_wrong_placed_whitespace(fd) - ) - assert result == [] - - @pytest.mark.parametrize( - "data, expected", - [ - ( - ( - """ - msg = ( - "foo" - " bar" - ) - """ - ), - [ - ( - 3, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ) - ], - ), - ( - ( - """ - msg = ( - f"foo" - " bar" - ) - """ - ), - [ - ( - 3, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ) - ], - ), - ( - ( - """ - msg = ( - "foo" - f" bar" - ) - """ - ), - [ - ( - 3, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ) - ], - ), - ( - ( - """ - msg = ( - f"foo" - f" bar" - ) - """ - ), - [ - ( - 3, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ) - ], - ), - ( - ( - """ - msg = ( - "foo" - rf" bar" - " baz" - ) - """ - ), - [ - ( - 3, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ), - ( - 4, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ), - ], - ), - ( - ( - """ - msg = ( - "foo" - " bar" - rf" baz" - ) - """ - ), - [ - ( - 3, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ), - ( - 4, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ), - ], - ), - ( - ( - """ - msg = ( - "foo" - rf" bar" - rf" baz" - ) - """ - ), - [ - ( - 3, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ), - ( - 4, - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ), - ], - ), - ], - ) - def test_strings_with_wrong_placed_whitespace_raises(self, data, expected): - fd = io.StringIO(data.strip()) - result = list( - validate_unwanted_patterns.strings_with_wrong_placed_whitespace(fd) - ) - assert result == expected diff --git a/scripts/validate_unwanted_patterns.py b/scripts/validate_unwanted_patterns.py deleted file mode 100755 index b6b038ae9dd17..0000000000000 --- a/scripts/validate_unwanted_patterns.py +++ /dev/null @@ -1,487 +0,0 @@ -#!/usr/bin/env python3 -""" -Unwanted patterns test cases. - -The reason this file exist despite the fact we already have -`ci/code_checks.sh`, -(see https://github.com/pandas-dev/pandas/blob/master/ci/code_checks.sh) - -is that some of the test cases are more complex/imposible to validate via regex. -So this file is somewhat an extensions to `ci/code_checks.sh` -""" - -import argparse -import ast -import sys -import token -import tokenize -from typing import ( - IO, - Callable, - Iterable, - List, - Set, - Tuple, -) - -PRIVATE_IMPORTS_TO_IGNORE: Set[str] = { - "_extension_array_shared_docs", - "_index_shared_docs", - "_interval_shared_docs", - "_merge_doc", - "_shared_docs", - "_apply_docs", - "_new_Index", - "_new_PeriodIndex", - "_doc_template", - "_agg_template", - "_pipe_template", - "__main__", - "_transform_template", - "_flex_comp_doc_FRAME", - "_op_descriptions", - "_IntegerDtype", - "_use_inf_as_na", - "_get_plot_backend", - "_matplotlib", - "_arrow_utils", - "_registry", - "_get_offset", # TODO: remove after get_offset deprecation enforced - "_test_parse_iso8601", - "_json_normalize", # TODO: remove after deprecation is enforced - "_testing", - "_test_decorators", - "__version__", # check np.__version__ in compat.numpy.function -} - - -def _get_literal_string_prefix_len(token_string: str) -> int: - """ - Getting the length of the literal string prefix. - - Parameters - ---------- - token_string : str - String to check. - - Returns - ------- - int - Length of the literal string prefix. - - Examples - -------- - >>> example_string = "'Hello world'" - >>> _get_literal_string_prefix_len(example_string) - 0 - >>> example_string = "r'Hello world'" - >>> _get_literal_string_prefix_len(example_string) - 1 - """ - try: - return min( - token_string.find(quote) - for quote in (r"'", r'"') - if token_string.find(quote) >= 0 - ) - except ValueError: - return 0 - - -def bare_pytest_raises(file_obj: IO[str]) -> Iterable[Tuple[int, str]]: - """ - Test Case for bare pytest raises. - - For example, this is wrong: - - >>> with pytest.raise(ValueError): - ... # Some code that raises ValueError - - And this is what we want instead: - - >>> with pytest.raise(ValueError, match="foo"): - ... # Some code that raises ValueError - - Parameters - ---------- - file_obj : IO - File-like object containing the Python code to validate. - - Yields - ------ - line_number : int - Line number of unconcatenated string. - msg : str - Explenation of the error. - - Notes - ----- - GH #23922 - """ - contents = file_obj.read() - tree = ast.parse(contents) - - for node in ast.walk(tree): - if not isinstance(node, ast.Call): - continue - - try: - if not (node.func.value.id == "pytest" and node.func.attr == "raises"): - continue - except AttributeError: - continue - - if not node.keywords: - yield ( - node.lineno, - "Bare pytests raise have been found. " - "Please pass in the argument 'match' as well the exception.", - ) - else: - # Means that there are arguments that are being passed in, - # now we validate that `match` is one of the passed in arguments - if not any(keyword.arg == "match" for keyword in node.keywords): - yield ( - node.lineno, - "Bare pytests raise have been found. " - "Please pass in the argument 'match' as well the exception.", - ) - - -PRIVATE_FUNCTIONS_ALLOWED = {"sys._getframe"} # no known alternative - - -def private_function_across_module(file_obj: IO[str]) -> Iterable[Tuple[int, str]]: - """ - Checking that a private function is not used across modules. - Parameters - ---------- - file_obj : IO - File-like object containing the Python code to validate. - Yields - ------ - line_number : int - Line number of the private function that is used across modules. - msg : str - Explenation of the error. - """ - contents = file_obj.read() - tree = ast.parse(contents) - - imported_modules: Set[str] = set() - - for node in ast.walk(tree): - if isinstance(node, (ast.Import, ast.ImportFrom)): - for module in node.names: - module_fqdn = module.name if module.asname is None else module.asname - imported_modules.add(module_fqdn) - - if not isinstance(node, ast.Call): - continue - - try: - module_name = node.func.value.id - function_name = node.func.attr - except AttributeError: - continue - - # Exception section # - - # (Debatable) Class case - if module_name[0].isupper(): - continue - # (Debatable) Dunder methods case - elif function_name.startswith("__") and function_name.endswith("__"): - continue - elif module_name + "." + function_name in PRIVATE_FUNCTIONS_ALLOWED: - continue - - if module_name in imported_modules and function_name.startswith("_"): - yield (node.lineno, f"Private function '{module_name}.{function_name}'") - - -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), - is formating strings over multiple lines. - - For example, when this: - - >>> foo = ( - ... "bar " - ... "baz" - ... ) - - Is becoming this: - - >>> foo = ("bar " "baz") - - 'Black' is not considering this as an - issue (see https://github.com/psf/black/issues/1051), - so we are checking it here instead. - - Parameters - ---------- - file_obj : IO - File-like object containing the Python code to validate. - - Yields - ------ - line_number : int - Line number of unconcatenated string. - msg : str - Explenation of the error. - - Notes - ----- - GH #30454 - """ - tokens: List = list(tokenize.generate_tokens(file_obj.readline)) - - for current_token, next_token in zip(tokens, tokens[1:]): - if current_token.type == next_token.type == token.STRING: - yield ( - current_token.start[0], - ( - "String unnecessarily split in two by black. " - "Please merge them manually." - ), - ) - - -def strings_with_wrong_placed_whitespace( - file_obj: IO[str], -) -> Iterable[Tuple[int, str]]: - """ - Test case for leading spaces in concated strings. - - For example: - - >>> rule = ( - ... "We want the space at the end of the line, " - ... "not at the beginning" - ... ) - - Instead of: - - >>> rule = ( - ... "We want the space at the end of the line," - ... " not at the beginning" - ... ) - - Parameters - ---------- - file_obj : IO - File-like object containing the Python code to validate. - - Yields - ------ - line_number : int - Line number of unconcatenated string. - msg : str - Explenation of the error. - """ - - def has_wrong_whitespace(first_line: str, second_line: str) -> bool: - """ - Checking if the two lines are mattching the unwanted pattern. - - Parameters - ---------- - first_line : str - First line to check. - second_line : str - Second line to check. - - Returns - ------- - bool - True if the two recived string match, an unwanted pattern. - - Notes - ----- - The unwanted pattern that we are trying to catch is if the spaces in - a string that is concatenated over multiple lines are placed at the - end of each string, unless this string is ending with a - newline character (\n). - - For example, this is bad: - - >>> rule = ( - ... "We want the space at the end of the line," - ... " not at the beginning" - ... ) - - And what we want is: - - >>> rule = ( - ... "We want the space at the end of the line, " - ... "not at the beginning" - ... ) - - And if the string is ending with a new line character (\n) we - do not want any trailing whitespaces after it. - - For example, this is bad: - - >>> rule = ( - ... "We want the space at the begging of " - ... "the line if the previous line is ending with a \n " - ... "not at the end, like always" - ... ) - - And what we do want is: - - >>> rule = ( - ... "We want the space at the begging of " - ... "the line if the previous line is ending with a \n" - ... " not at the end, like always" - ... ) - """ - if first_line.endswith(r"\n"): - return False - elif first_line.startswith(" ") or second_line.startswith(" "): - return False - elif first_line.endswith(" ") or second_line.endswith(" "): - return False - elif (not first_line.endswith(" ")) and second_line.startswith(" "): - return True - return False - - tokens: List = list(tokenize.generate_tokens(file_obj.readline)) - - for first_token, second_token, third_token in zip(tokens, tokens[1:], tokens[2:]): - # Checking if we are in a block of concated string - if ( - first_token.type == third_token.type == token.STRING - and second_token.type == token.NL - ): - # Striping the quotes, with the string litteral prefix - first_string: str = first_token.string[ - _get_literal_string_prefix_len(first_token.string) + 1 : -1 - ] - second_string: str = third_token.string[ - _get_literal_string_prefix_len(third_token.string) + 1 : -1 - ] - - if has_wrong_whitespace(first_string, second_string): - yield ( - third_token.start[0], - ( - "String has a space at the beginning instead " - "of the end of the previous string." - ), - ) - - -def main( - function: Callable[[IO[str]], Iterable[Tuple[int, str]]], - source_path: str, - output_format: str, -) -> bool: - """ - Main entry point of the script. - - Parameters - ---------- - function : Callable - Function to execute for the specified validation type. - source_path : str - Source path representing path to a file/directory. - output_format : str - Output format of the error message. - file_extensions_to_check : str - Comma separated values of what file extensions to check. - excluded_file_paths : str - Comma separated values of what file paths to exclude during the check. - - Returns - ------- - bool - True if found any patterns are found related to the given function. - - Raises - ------ - ValueError - If the `source_path` is not pointing to existing file/directory. - """ - is_failed: bool = False - - for file_path in source_path: - with open(file_path, encoding="utf-8") as file_obj: - for line_number, msg in function(file_obj): - is_failed = True - print( - output_format.format( - source_path=file_path, line_number=line_number, msg=msg - ) - ) - - return is_failed - - -if __name__ == "__main__": - available_validation_types: List[str] = [ - "bare_pytest_raises", - "private_function_across_module", - "private_import_across_module", - "strings_to_concatenate", - "strings_with_wrong_placed_whitespace", - ] - - parser = argparse.ArgumentParser(description="Unwanted patterns checker.") - - parser.add_argument("paths", nargs="*", help="Source paths of files to check.") - parser.add_argument( - "--format", - "-f", - default="{source_path}:{line_number}:{msg}", - help="Output format of the error message.", - ) - parser.add_argument( - "--validation-type", - "-vt", - choices=available_validation_types, - required=True, - help="Validation test case to check.", - ) - - args = parser.parse_args() - - sys.exit( - main( - function=globals().get(args.validation_type), - source_path=args.paths, - output_format=args.format, - ) - ) diff --git a/setup.cfg b/setup.cfg index 9e3deff4c7183..610b30e4422a9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -93,6 +93,16 @@ exclude = .eggs/*.py, versioneer.py, env # exclude asv benchmark environments from linting +per-file-ignores = + # private import across modules + pandas/tests/*:PDF020 + # pytest.raises without match= + pandas/tests/extension/*:PDF009 + # os.remove + doc/make.py:PDF008 + # import from pandas._testing + pandas/testing.py:PDF014 + [flake8-rst] max-line-length = 84