diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 79a90b7..f736905 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -64,6 +64,12 @@ entry: check-merge-conflict language: python types: [text] +- id: check-python-modules + name: Check python modules + description: Ensure python modules have their __init__.py file. + entry: check-python-modules + language: python + types: [python] - id: check-symlinks name: Check for broken symlinks description: Checks for symlinks which do not point to anything. diff --git a/README.md b/README.md index 177c4be..24c6d50 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Add this to your `.pre-commit-config.yaml` proper shebang. - `check-json` - Attempts to load all json files to verify syntax. - `check-merge-conflict` - Check for files that contain merge conflict strings. +- `check-python-modules` - Ensure python modules have their `__init__.py` file. - `check-symlinks` - Checks for symlinks which do not point to anything. - `check-vcs-permalinks` - Ensures that links to vcs websites are permalinks. - `check-xml` - Attempts to load all xml files to verify syntax. diff --git a/pre_commit_hooks/check_python_modules.py b/pre_commit_hooks/check_python_modules.py new file mode 100644 index 0000000..bdf11f4 --- /dev/null +++ b/pre_commit_hooks/check_python_modules.py @@ -0,0 +1,50 @@ +from __future__ import absolute_import +from __future__ import print_function + +import os.path +from argparse import ArgumentParser +from typing import Optional +from typing import Sequence +from typing import Set + +OK = 0 +ERR = 1 + + +class ModuleInitChecker: + def __init__(self): + # type: () -> None + self.seen_dirnames = set() # type: Set[str] + + def check(self, filename): + # type: (str) -> int + dirname = os.path.dirname(filename) + if dirname in self.seen_dirnames: + return OK + + init_file = os.path.join(dirname, '__init__.py') + if dirname and not os.path.exists(init_file): + self.seen_dirnames.add(dirname) + with open(init_file, 'w'): + print(f'Created {init_file}') + return ERR + + return OK + + +def main(argv=None): # type: (Optional[Sequence[str]]) -> int + parser = ArgumentParser() + parser.add_argument('filenames', nargs='*') + + args = parser.parse_args(argv) + + status = OK + checker = ModuleInitChecker() + for filename in args.filenames: + status |= checker.check(filename) + + return status + + +if __name__ == '__main__': + exit(main()) diff --git a/setup.py b/setup.py index d61244f..b268710 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ setup( 'check-executables-have-shebangs = pre_commit_hooks.check_executables_have_shebangs:main', # noqa: E501 'check-json = pre_commit_hooks.check_json:main', 'check-merge-conflict = pre_commit_hooks.check_merge_conflict:main', # noqa: E501 + 'check-python-modules = pre_commit_hooks.check_python_modules:main', # noqa: E501 'check-symlinks = pre_commit_hooks.check_symlinks:main', 'check-vcs-permalinks = pre_commit_hooks.check_vcs_permalinks:main', # noqa: E501 'check-xml = pre_commit_hooks.check_xml:main', diff --git a/tests/check_python_modules_test.py b/tests/check_python_modules_test.py new file mode 100644 index 0000000..64080ca --- /dev/null +++ b/tests/check_python_modules_test.py @@ -0,0 +1,13 @@ +import pytest + +from pre_commit_hooks.check_python_modules import main + + +@pytest.mark.parametrize('has_init', (True, False)) +def test_main(tmpdir, has_init): + if has_init: + tmpdir.join('__init__.py').ensure() + path = tmpdir.join('thing.py').ensure() + + expected = 0 if has_init else 1 + assert main((path.strpath,)) == expected