-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild-init-files.py
More file actions
executable file
·99 lines (76 loc) · 2.76 KB
/
Copy pathbuild-init-files.py
File metadata and controls
executable file
·99 lines (76 loc) · 2.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
# Create missing `__init__.py` files in the source code folders.
#
# They are required by the python interpreter to properly identify modules/packages so that tools like `mypy` or an IDE
# can work with their full capabilities.
#
# See https://docs.python.org/3/tutorial/modules.html#packages.
#
# Note: This script is run in a `pre-commit` hook (which runs on CI) to make sure we don't miss out any folder.
from __future__ import annotations
import logging
import pathlib
import click
log_levels = {
0: logging.ERROR,
1: logging.WARNING,
2: logging.INFO,
}
ignores = {"__pycache__", ".pytest_cache"}
def traverse_directory(path: pathlib.Path) -> list[pathlib.Path]:
of_interest: list[pathlib.Path] = []
file_found = False
for member in path.iterdir():
if not member.is_dir():
file_found = True
continue
if member.name in ignores:
continue
found = traverse_directory(path=member)
of_interest.extend(found)
if len(found) > 0:
of_interest.append(member)
if len(of_interest) > 0 or file_found:
of_interest.append(path)
return of_interest
@click.command()
@click.option(
"-r", "--root", "root_str", type=click.Path(dir_okay=True, file_okay=False, resolve_path=True), default="."
)
@click.option(
"-t",
"--tree",
"tree_roots",
type=click.Path(dir_okay=True, file_okay=False, resolve_path=True),
multiple=True,
required=True,
)
@click.option("-v", "--verbose", count=True, help=f"Increase verbosity up to {len(log_levels) - 1} times")
def command(verbose: int, root_str: str, tree_roots: list[str]) -> None:
logger = logging.getLogger()
log_level = log_levels.get(verbose, min(log_levels.values()))
logger.setLevel(log_level)
stream_handler = logging.StreamHandler()
logger.addHandler(stream_handler)
failed = False
root = pathlib.Path(root_str).resolve()
directories = [
directory for tree_root in tree_roots for directory in traverse_directory(path=root.joinpath(tree_root))
]
for path in directories:
init_path = path.joinpath("__init__.py")
# This has plenty of race hazards. If it messes up,
# it will likely get caught the next time.
if init_path.is_file() and not init_path.is_symlink():
logger.info("Found : %s", init_path)
continue
if not init_path.exists():
failed = True
init_path.touch()
logger.warning("Created : %s", init_path)
else:
failed = True
logger.error("Fail : present but not a regular file: %s", init_path)
if failed:
raise click.ClickException("At least one __init__.py created or not a regular file")
command()