summaryrefslogtreecommitdiffstats
path: root/pre_commit_hooks
diff options
context:
space:
mode:
Diffstat (limited to 'pre_commit_hooks')
-rw-r--r--pre_commit_hooks/check_added_large_files.py8
-rw-r--r--pre_commit_hooks/check_ast.py5
-rw-r--r--pre_commit_hooks/check_builtin_literals.py17
-rw-r--r--pre_commit_hooks/check_byte_order_marker.py5
-rw-r--r--pre_commit_hooks/check_case_conflict.py10
-rw-r--r--pre_commit_hooks/check_docstring_first.py9
-rw-r--r--pre_commit_hooks/check_executables_have_shebangs.py16
-rw-r--r--pre_commit_hooks/check_json.py12
-rw-r--r--pre_commit_hooks/check_merge_conflict.py12
-rw-r--r--pre_commit_hooks/check_shebang_scripts_are_executable.py13
-rw-r--r--pre_commit_hooks/check_symlinks.py5
-rw-r--r--pre_commit_hooks/check_toml.py16
-rw-r--r--pre_commit_hooks/check_vcs_permalinks.py8
-rw-r--r--pre_commit_hooks/check_xml.py5
-rw-r--r--pre_commit_hooks/check_yaml.py5
-rw-r--r--pre_commit_hooks/debug_statement_hook.py10
-rw-r--r--pre_commit_hooks/destroyed_symlinks.py10
-rw-r--r--pre_commit_hooks/detect_aws_credentials.py19
-rw-r--r--pre_commit_hooks/detect_private_key.py5
-rw-r--r--pre_commit_hooks/end_of_file_fixer.py5
-rw-r--r--pre_commit_hooks/file_contents_sorter.py7
-rw-r--r--pre_commit_hooks/fix_byte_order_marker.py5
-rw-r--r--pre_commit_hooks/fix_encoding_pragma.py9
-rw-r--r--pre_commit_hooks/forbid_new_submodules.py5
-rw-r--r--pre_commit_hooks/mixed_line_ending.py8
-rw-r--r--pre_commit_hooks/no_commit_to_branch.py5
-rw-r--r--pre_commit_hooks/pretty_format_json.py14
-rw-r--r--pre_commit_hooks/removed.py5
-rw-r--r--pre_commit_hooks/requirements_txt_fixer.py16
-rw-r--r--pre_commit_hooks/sort_simple_yaml.py14
-rw-r--r--pre_commit_hooks/string_fixer.py8
-rw-r--r--pre_commit_hooks/tests_should_end_in_test.py36
-rw-r--r--pre_commit_hooks/trailing_whitespace_fixer.py9
-rw-r--r--pre_commit_hooks/util.py11
34 files changed, 193 insertions, 154 deletions
diff --git a/pre_commit_hooks/check_added_large_files.py b/pre_commit_hooks/check_added_large_files.py
index e1beb4e..79c8d4e 100644
--- a/pre_commit_hooks/check_added_large_files.py
+++ b/pre_commit_hooks/check_added_large_files.py
@@ -1,16 +1,16 @@
+from __future__ import annotations
+
import argparse
import math
import os
import subprocess
-from typing import Optional
from typing import Sequence
-from typing import Set
from pre_commit_hooks.util import added_files
from pre_commit_hooks.util import zsplit
-def filter_lfs_files(filenames: Set[str]) -> None: # pragma: no cover (lfs)
+def filter_lfs_files(filenames: set[str]) -> None: # pragma: no cover (lfs)
"""Remove files tracked by git-lfs from the set."""
if not filenames:
return
@@ -54,7 +54,7 @@ def find_large_added_files(
return retv
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'filenames', nargs='*',
diff --git a/pre_commit_hooks/check_ast.py b/pre_commit_hooks/check_ast.py
index ab5661d..fdac361 100644
--- a/pre_commit_hooks/check_ast.py
+++ b/pre_commit_hooks/check_ast.py
@@ -1,13 +1,14 @@
+from __future__ import annotations
+
import argparse
import ast
import platform
import sys
import traceback
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_builtin_literals.py b/pre_commit_hooks/check_builtin_literals.py
index 3fbae3e..d3054aa 100644
--- a/pre_commit_hooks/check_builtin_literals.py
+++ b/pre_commit_hooks/check_builtin_literals.py
@@ -1,10 +1,9 @@
+from __future__ import annotations
+
import argparse
import ast
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
-from typing import Set
BUILTIN_TYPES = {
@@ -27,10 +26,10 @@ class Call(NamedTuple):
class Visitor(ast.NodeVisitor):
def __init__(
self,
- ignore: Optional[Sequence[str]] = None,
+ ignore: Sequence[str] | None = None,
allow_dict_kwargs: bool = True,
) -> None:
- self.builtin_type_calls: List[Call] = []
+ self.builtin_type_calls: list[Call] = []
self.ignore = set(ignore) if ignore else set()
self.allow_dict_kwargs = allow_dict_kwargs
@@ -56,9 +55,9 @@ class Visitor(ast.NodeVisitor):
def check_file(
filename: str,
- ignore: Optional[Sequence[str]] = None,
+ ignore: Sequence[str] | None = None,
allow_dict_kwargs: bool = True,
-) -> List[Call]:
+) -> list[Call]:
with open(filename, 'rb') as f:
tree = ast.parse(f.read(), filename=filename)
visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs)
@@ -66,11 +65,11 @@ def check_file(
return visitor.builtin_type_calls
-def parse_ignore(value: str) -> Set[str]:
+def parse_ignore(value: str) -> set[str]:
return set(value.split(','))
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
parser.add_argument('--ignore', type=parse_ignore, default=set())
diff --git a/pre_commit_hooks/check_byte_order_marker.py b/pre_commit_hooks/check_byte_order_marker.py
index fda05e8..59cc561 100644
--- a/pre_commit_hooks/check_byte_order_marker.py
+++ b/pre_commit_hooks/check_byte_order_marker.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to check')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_case_conflict.py b/pre_commit_hooks/check_case_conflict.py
index c3f39db..33a13f1 100644
--- a/pre_commit_hooks/check_case_conflict.py
+++ b/pre_commit_hooks/check_case_conflict.py
@@ -1,15 +1,15 @@
+from __future__ import annotations
+
import argparse
from typing import Iterable
from typing import Iterator
-from typing import Optional
from typing import Sequence
-from typing import Set
from pre_commit_hooks.util import added_files
from pre_commit_hooks.util import cmd_output
-def lower_set(iterable: Iterable[str]) -> Set[str]:
+def lower_set(iterable: Iterable[str]) -> set[str]:
return {x.lower() for x in iterable}
@@ -21,7 +21,7 @@ def parents(file: str) -> Iterator[str]:
path_parts.pop()
-def directories_for(files: Set[str]) -> Set[str]:
+def directories_for(files: set[str]) -> set[str]:
return {parent for file in files for parent in parents(file)}
@@ -56,7 +56,7 @@ def find_conflicting_filenames(filenames: Sequence[str]) -> int:
return retv
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'filenames', nargs='*',
diff --git a/pre_commit_hooks/check_docstring_first.py b/pre_commit_hooks/check_docstring_first.py
index 875c0fb..d55f08a 100644
--- a/pre_commit_hooks/check_docstring_first.py
+++ b/pre_commit_hooks/check_docstring_first.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import argparse
import io
import tokenize
from tokenize import tokenize as tokenize_tokenize
-from typing import Optional
from typing import Sequence
NON_CODE_TOKENS = frozenset((
@@ -27,13 +28,13 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
if tok_type == tokenize.STRING and scol == 0:
if found_docstring_line is not None:
print(
- f'{filename}:{sline} Multiple module docstrings '
+ f'{filename}:{sline}: Multiple module docstrings '
f'(first docstring on line {found_docstring_line}).',
)
return 1
elif found_code_line is not None:
print(
- f'{filename}:{sline} Module docstring appears after code '
+ f'{filename}:{sline}: Module docstring appears after code '
f'(code seen on line {found_code_line}).',
)
return 1
@@ -45,7 +46,7 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
return 0
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_executables_have_shebangs.py b/pre_commit_hooks/check_executables_have_shebangs.py
index 34af5ca..d8e4f49 100644
--- a/pre_commit_hooks/check_executables_have_shebangs.py
+++ b/pre_commit_hooks/check_executables_have_shebangs.py
@@ -1,13 +1,12 @@
"""Check that executable text files have a shebang."""
+from __future__ import annotations
+
import argparse
import shlex
import sys
from typing import Generator
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
-from typing import Set
from pre_commit_hooks.util import cmd_output
from pre_commit_hooks.util import zsplit
@@ -15,8 +14,11 @@ from pre_commit_hooks.util import zsplit
EXECUTABLE_VALUES = frozenset(('1', '3', '5', '7'))
-def check_executables(paths: List[str]) -> int:
- if sys.platform == 'win32': # pragma: win32 cover
+def check_executables(paths: list[str]) -> int:
+ fs_tracks_executable_bit = cmd_output(
+ 'git', 'config', 'core.fileMode', retcode=None,
+ ).strip()
+ if fs_tracks_executable_bit == 'false': # pragma: win32 cover
return _check_git_filemode(paths)
else: # pragma: win32 no cover
retv = 0
@@ -42,7 +44,7 @@ def git_ls_files(paths: Sequence[str]) -> Generator[GitLsFile, None, None]:
def _check_git_filemode(paths: Sequence[str]) -> int:
- seen: Set[str] = set()
+ seen: set[str] = set()
for ls_file in git_ls_files(paths):
is_executable = any(b in EXECUTABLE_VALUES for b in ls_file.mode[-3:])
if is_executable and not has_shebang(ls_file.filename):
@@ -71,7 +73,7 @@ def _message(path: str) -> None:
)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_json.py b/pre_commit_hooks/check_json.py
index 96ba024..6a679fe 100644
--- a/pre_commit_hooks/check_json.py
+++ b/pre_commit_hooks/check_json.py
@@ -1,16 +1,14 @@
+from __future__ import annotations
+
import argparse
import json
from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
from typing import Sequence
-from typing import Tuple
def raise_duplicate_keys(
- ordered_pairs: List[Tuple[str, Any]],
-) -> Dict[str, Any]:
+ ordered_pairs: list[tuple[str, Any]],
+) -> dict[str, Any]:
d = {}
for key, val in ordered_pairs:
if key in d:
@@ -20,7 +18,7 @@ def raise_duplicate_keys(
return d
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_merge_conflict.py b/pre_commit_hooks/check_merge_conflict.py
index aed1e9c..15ec284 100644
--- a/pre_commit_hooks/check_merge_conflict.py
+++ b/pre_commit_hooks/check_merge_conflict.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import argparse
import os.path
-from typing import Optional
from typing import Sequence
from pre_commit_hooks.util import cmd_output
@@ -9,6 +10,7 @@ from pre_commit_hooks.util import cmd_output
CONFLICT_PATTERNS = [
b'<<<<<<< ',
b'======= ',
+ b'=======\r\n',
b'=======\n',
b'>>>>>>> ',
]
@@ -26,7 +28,7 @@ def is_in_merge() -> bool:
)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
parser.add_argument('--assume-in-merge', action='store_true')
@@ -38,12 +40,12 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
retcode = 0
for filename in args.filenames:
with open(filename, 'rb') as inputfile:
- for i, line in enumerate(inputfile):
+ for i, line in enumerate(inputfile, start=1):
for pattern in CONFLICT_PATTERNS:
if line.startswith(pattern):
print(
- f'Merge conflict string "{pattern.decode()}" '
- f'found in {filename}:{i + 1}',
+ f'{filename}:{i}: Merge conflict string '
+ f'{pattern.strip().decode()!r} found',
)
retcode = 1
diff --git a/pre_commit_hooks/check_shebang_scripts_are_executable.py b/pre_commit_hooks/check_shebang_scripts_are_executable.py
index 50bc9c0..621696c 100644
--- a/pre_commit_hooks/check_shebang_scripts_are_executable.py
+++ b/pre_commit_hooks/check_shebang_scripts_are_executable.py
@@ -1,18 +1,17 @@
"""Check that text files with a shebang are executable."""
+from __future__ import annotations
+
import argparse
import shlex
import sys
-from typing import List
-from typing import Optional
from typing import Sequence
-from typing import Set
from pre_commit_hooks.check_executables_have_shebangs import EXECUTABLE_VALUES
from pre_commit_hooks.check_executables_have_shebangs import git_ls_files
from pre_commit_hooks.check_executables_have_shebangs import has_shebang
-def check_shebangs(paths: List[str]) -> int:
+def check_shebangs(paths: list[str]) -> int:
# Cannot optimize on non-executability here if we intend this check to
# work on win32 -- and that's where problems caused by non-executability
# (elsewhere) are most likely to arise from.
@@ -20,7 +19,7 @@ def check_shebangs(paths: List[str]) -> int:
def _check_git_filemode(paths: Sequence[str]) -> int:
- seen: Set[str] = set()
+ seen: set[str] = set()
for ls_file in git_ls_files(paths):
is_executable = any(b in EXECUTABLE_VALUES for b in ls_file.mode[-3:])
if not is_executable and has_shebang(ls_file.filename):
@@ -35,13 +34,15 @@ def _message(path: str) -> None:
f'{path}: has a shebang but is not marked executable!\n'
f' If it is supposed to be executable, try: '
f'`chmod +x {shlex.quote(path)}`\n'
+ f' If on Windows, you may also need to: '
+ f'`git add --chmod=+x {shlex.quote(path)}`\n'
f' If it not supposed to be executable, double-check its shebang '
f'is wanted.\n',
file=sys.stderr,
)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_symlinks.py b/pre_commit_hooks/check_symlinks.py
index db3b4fe..a85c82a 100644
--- a/pre_commit_hooks/check_symlinks.py
+++ b/pre_commit_hooks/check_symlinks.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import argparse
import os.path
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(description='Checks for broken symlinks.')
parser.add_argument('filenames', nargs='*', help='Filenames to check')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_toml.py b/pre_commit_hooks/check_toml.py
index f623e68..0407371 100644
--- a/pre_commit_hooks/check_toml.py
+++ b/pre_commit_hooks/check_toml.py
@@ -1,11 +1,16 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
+import sys
from typing import Sequence
-import toml
+if sys.version_info >= (3, 11): # pragma: >=3.11 cover
+ import tomllib
+else: # pragma: <3.11 cover
+ import tomli as tomllib
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
args = parser.parse_args(argv)
@@ -13,8 +18,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
retval = 0
for filename in args.filenames:
try:
- toml.load(filename)
- except toml.TomlDecodeError as exc:
+ with open(filename, mode='rb') as fp:
+ tomllib.load(fp)
+ except tomllib.TOMLDecodeError as exc:
print(f'{filename}: {exc}')
retval = 1
return retval
diff --git a/pre_commit_hooks/check_vcs_permalinks.py b/pre_commit_hooks/check_vcs_permalinks.py
index 1c77b9a..68639bd 100644
--- a/pre_commit_hooks/check_vcs_permalinks.py
+++ b/pre_commit_hooks/check_vcs_permalinks.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
import argparse
import re
import sys
-from typing import List
-from typing import Optional
from typing import Pattern
from typing import Sequence
@@ -15,7 +15,7 @@ def _get_pattern(domain: str) -> Pattern[bytes]:
return re.compile(regex.encode())
-def _check_filename(filename: str, patterns: List[Pattern[bytes]]) -> int:
+def _check_filename(filename: str, patterns: list[Pattern[bytes]]) -> int:
retv = 0
with open(filename, 'rb') as f:
for i, line in enumerate(f, 1):
@@ -28,7 +28,7 @@ def _check_filename(filename: str, patterns: List[Pattern[bytes]]) -> int:
return retv
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
parser.add_argument(
diff --git a/pre_commit_hooks/check_xml.py b/pre_commit_hooks/check_xml.py
index 0fa6bb2..c256af9 100644
--- a/pre_commit_hooks/check_xml.py
+++ b/pre_commit_hooks/check_xml.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import argparse
import xml.sax.handler
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='XML filenames to check.')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_yaml.py b/pre_commit_hooks/check_yaml.py
index 5e86b73..250794e 100644
--- a/pre_commit_hooks/check_yaml.py
+++ b/pre_commit_hooks/check_yaml.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import argparse
from typing import Any
from typing import Generator
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
import ruamel.yaml
@@ -36,7 +37,7 @@ LOAD_FNS = {
}
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'-m', '--multi', '--allow-multiple-documents', action='store_true',
diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py
index f78d6d6..9ada657 100644
--- a/pre_commit_hooks/debug_statement_hook.py
+++ b/pre_commit_hooks/debug_statement_hook.py
@@ -1,9 +1,9 @@
+from __future__ import annotations
+
import argparse
import ast
import traceback
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
@@ -29,7 +29,7 @@ class Debug(NamedTuple):
class DebugStatementParser(ast.NodeVisitor):
def __init__(self) -> None:
- self.breakpoints: List[Debug] = []
+ self.breakpoints: list[Debug] = []
def visit_Import(self, node: ast.Import) -> None:
for name in node.names:
@@ -65,12 +65,12 @@ def check_file(filename: str) -> int:
visitor.visit(ast_obj)
for bp in visitor.breakpoints:
- print(f'{filename}:{bp.line}:{bp.col} - {bp.name} {bp.reason}')
+ print(f'{filename}:{bp.line}:{bp.col}: {bp.name} {bp.reason}')
return int(bool(visitor.breakpoints))
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to run')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/destroyed_symlinks.py b/pre_commit_hooks/destroyed_symlinks.py
index a3f122f..88253c0 100644
--- a/pre_commit_hooks/destroyed_symlinks.py
+++ b/pre_commit_hooks/destroyed_symlinks.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
import argparse
import shlex
import subprocess
-from typing import List
-from typing import Optional
from typing import Sequence
from pre_commit_hooks.util import cmd_output
@@ -13,8 +13,8 @@ PERMS_LINK = '120000'
PERMS_NONEXIST = '000000'
-def find_destroyed_symlinks(files: Sequence[str]) -> List[str]:
- destroyed_links: List[str] = []
+def find_destroyed_symlinks(files: Sequence[str]) -> list[str]:
+ destroyed_links: list[str] = []
if not files:
return destroyed_links
for line in zsplit(
@@ -66,7 +66,7 @@ def find_destroyed_symlinks(files: Sequence[str]) -> List[str]:
return destroyed_links
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to check.')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py
index ba1d789..4f59d9c 100644
--- a/pre_commit_hooks/detect_aws_credentials.py
+++ b/pre_commit_hooks/detect_aws_credentials.py
@@ -1,11 +1,10 @@
+from __future__ import annotations
+
import argparse
import configparser
import os
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
-from typing import Set
class BadFile(NamedTuple):
@@ -13,7 +12,7 @@ class BadFile(NamedTuple):
key: str
-def get_aws_cred_files_from_env() -> Set[str]:
+def get_aws_cred_files_from_env() -> set[str]:
"""Extract credential file paths from environment variables."""
return {
os.environ[env_var]
@@ -25,7 +24,7 @@ def get_aws_cred_files_from_env() -> Set[str]:
}
-def get_aws_secrets_from_env() -> Set[str]:
+def get_aws_secrets_from_env() -> set[str]:
"""Extract AWS secrets from environment variables."""
keys = set()
for env_var in (
@@ -36,7 +35,7 @@ def get_aws_secrets_from_env() -> Set[str]:
return keys
-def get_aws_secrets_from_file(credentials_file: str) -> Set[str]:
+def get_aws_secrets_from_file(credentials_file: str) -> set[str]:
"""Extract AWS secrets from configuration files.
Read an ini-style configuration file and return a set with all found AWS
@@ -69,8 +68,8 @@ def get_aws_secrets_from_file(credentials_file: str) -> Set[str]:
def check_file_for_aws_keys(
filenames: Sequence[str],
- keys: Set[bytes],
-) -> List[BadFile]:
+ keys: set[bytes],
+) -> list[BadFile]:
"""Check if files contain AWS secrets.
Return a list of all files containing AWS secrets and keys found, with all
@@ -90,7 +89,7 @@ def check_file_for_aws_keys(
return bad_files
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='+', help='Filenames to run')
parser.add_argument(
@@ -119,7 +118,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
# of files to to gather AWS secrets from.
credential_files |= get_aws_cred_files_from_env()
- keys: Set[str] = set()
+ keys: set[str] = set()
for credential_file in credential_files:
keys |= get_aws_secrets_from_file(credential_file)
diff --git a/pre_commit_hooks/detect_private_key.py b/pre_commit_hooks/detect_private_key.py
index 18f9539..cd51f90 100644
--- a/pre_commit_hooks/detect_private_key.py
+++ b/pre_commit_hooks/detect_private_key.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
from typing import Sequence
BLACKLIST = [
@@ -16,7 +17,7 @@ BLACKLIST = [
]
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to check')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/end_of_file_fixer.py b/pre_commit_hooks/end_of_file_fixer.py
index 40e8821..a30dce9 100644
--- a/pre_commit_hooks/end_of_file_fixer.py
+++ b/pre_commit_hooks/end_of_file_fixer.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import argparse
import os
from typing import IO
-from typing import Optional
from typing import Sequence
@@ -48,7 +49,7 @@ def fix_file(file_obj: IO[bytes]) -> int:
return 0
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py
index 392e226..c5691f0 100644
--- a/pre_commit_hooks/file_contents_sorter.py
+++ b/pre_commit_hooks/file_contents_sorter.py
@@ -9,12 +9,13 @@ per line. Various users are adding/removing lines from this file; using
this hook on that file should reduce the instances of git merge
conflicts and keep the file nicely ordered.
"""
+from __future__ import annotations
+
import argparse
from typing import Any
from typing import Callable
from typing import IO
from typing import Iterable
-from typing import Optional
from typing import Sequence
PASS = 0
@@ -23,7 +24,7 @@ FAIL = 1
def sort_file_contents(
f: IO[bytes],
- key: Optional[Callable[[bytes], Any]],
+ key: Callable[[bytes], Any] | None,
*,
unique: bool = False,
) -> int:
@@ -47,7 +48,7 @@ def sort_file_contents(
return FAIL
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='+', help='Files to sort')
parser.add_argument(
diff --git a/pre_commit_hooks/fix_byte_order_marker.py b/pre_commit_hooks/fix_byte_order_marker.py
index 51b94e1..22a4990 100644
--- a/pre_commit_hooks/fix_byte_order_marker.py
+++ b/pre_commit_hooks/fix_byte_order_marker.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to check')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/fix_encoding_pragma.py b/pre_commit_hooks/fix_encoding_pragma.py
index c704774..60c71ee 100644
--- a/pre_commit_hooks/fix_encoding_pragma.py
+++ b/pre_commit_hooks/fix_encoding_pragma.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import argparse
from typing import IO
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-'
@@ -26,7 +27,7 @@ class ExpectedContents(NamedTuple):
# True: has exactly the coding pragma expected
# False: missing coding pragma entirely
# None: has a coding pragma, but it does not match
- pragma_status: Optional[bool]
+ pragma_status: bool | None
ending: bytes
@property
@@ -55,7 +56,7 @@ def _get_expected_contents(
rest = second_line + rest
if potential_coding.rstrip(b'\r\n') == expected_pragma:
- pragma_status: Optional[bool] = True
+ pragma_status: bool | None = True
elif has_coding(potential_coding):
pragma_status = None
else:
@@ -105,7 +106,7 @@ def _normalize_pragma(pragma: str) -> bytes:
return pragma.encode().rstrip()
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(
'Fixes the encoding pragma of python files',
)
diff --git a/pre_commit_hooks/forbid_new_submodules.py b/pre_commit_hooks/forbid_new_submodules.py
index 0275808..b806cad 100644
--- a/pre_commit_hooks/forbid_new_submodules.py
+++ b/pre_commit_hooks/forbid_new_submodules.py
@@ -1,12 +1,13 @@
+from __future__ import annotations
+
import argparse
import os
-from typing import Optional
from typing import Sequence
from pre_commit_hooks.util import cmd_output
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/mixed_line_ending.py b/pre_commit_hooks/mixed_line_ending.py
index 4e07ed9..0328e86 100644
--- a/pre_commit_hooks/mixed_line_ending.py
+++ b/pre_commit_hooks/mixed_line_ending.py
@@ -1,7 +1,7 @@
+from __future__ import annotations
+
import argparse
import collections
-from typing import Dict
-from typing import Optional
from typing import Sequence
@@ -25,7 +25,7 @@ def fix_filename(filename: str, fix: str) -> int:
with open(filename, 'rb') as f:
contents = f.read()
- counts: Dict[bytes, int] = collections.defaultdict(int)
+ counts: dict[bytes, int] = collections.defaultdict(int)
for line in contents.splitlines(True):
for ending in ALL_ENDINGS:
@@ -62,7 +62,7 @@ def fix_filename(filename: str, fix: str) -> int:
return other_endings
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'-f', '--fix',
diff --git a/pre_commit_hooks/no_commit_to_branch.py b/pre_commit_hooks/no_commit_to_branch.py
index db84850..741f726 100644
--- a/pre_commit_hooks/no_commit_to_branch.py
+++ b/pre_commit_hooks/no_commit_to_branch.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import argparse
import re
from typing import AbstractSet
-from typing import Optional
from typing import Sequence
from pre_commit_hooks.util import CalledProcessError
@@ -23,7 +24,7 @@ def is_on_branch(
)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'-b', '--branch', action='append',
diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py
index 33ad5a1..627a11c 100644
--- a/pre_commit_hooks/pretty_format_json.py
+++ b/pre_commit_hooks/pretty_format_json.py
@@ -1,13 +1,11 @@
+from __future__ import annotations
+
import argparse
import json
import sys
from difflib import unified_diff
-from typing import List
from typing import Mapping
-from typing import Optional
from typing import Sequence
-from typing import Tuple
-from typing import Union
def _get_pretty_format(
@@ -17,7 +15,7 @@ def _get_pretty_format(
sort_keys: bool = True,
top_keys: Sequence[str] = (),
) -> str:
- def pairs_first(pairs: Sequence[Tuple[str, str]]) -> Mapping[str, str]:
+ def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]:
before = [pair for pair in pairs if pair[0] in top_keys]
before = sorted(before, key=lambda x: top_keys.index(x[0]))
after = [pair for pair in pairs if pair[0] not in top_keys]
@@ -38,7 +36,7 @@ def _autofix(filename: str, new_contents: str) -> None:
f.write(new_contents)
-def parse_num_to_int(s: str) -> Union[int, str]:
+def parse_num_to_int(s: str) -> int | str:
"""Convert string numbers to int, leaving strings as is."""
try:
return int(s)
@@ -46,7 +44,7 @@ def parse_num_to_int(s: str) -> Union[int, str]:
return s
-def parse_topkeys(s: str) -> List[str]:
+def parse_topkeys(s: str) -> list[str]:
return s.split(',')
@@ -57,7 +55,7 @@ def get_diff(source: str, target: str, file: str) -> str:
return ''.join(diff)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'--autofix',
diff --git a/pre_commit_hooks/removed.py b/pre_commit_hooks/removed.py
index 236cbf8..6f6c7b7 100644
--- a/pre_commit_hooks/removed.py
+++ b/pre_commit_hooks/removed.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import sys
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
argv = argv if argv is not None else sys.argv[1:]
hookid, new_hookid, url = argv[:3]
raise SystemExit(
diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py
index 63f891f..5884394 100644
--- a/pre_commit_hooks/requirements_txt_fixer.py
+++ b/pre_commit_hooks/requirements_txt_fixer.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
import argparse
import re
from typing import IO
-from typing import List
-from typing import Optional
from typing import Sequence
@@ -15,8 +15,8 @@ class Requirement:
UNTIL_SEP = re.compile(rb'[^;\s]+')
def __init__(self) -> None:
- self.value: Optional[bytes] = None
- self.comments: List[bytes] = []
+ self.value: bytes | None = None
+ self.comments: list[bytes] = []
@property
def name(self) -> bytes:
@@ -36,7 +36,7 @@ class Requirement:
return name[:m.start()]
- def __lt__(self, requirement: 'Requirement') -> bool:
+ def __lt__(self, requirement: Requirement) -> bool:
# \n means top of file comment, so always return True,
# otherwise just do a string comparison with value.
assert self.value is not None, self.value
@@ -61,9 +61,9 @@ class Requirement:
def fix_requirements(f: IO[bytes]) -> int:
- requirements: List[Requirement] = []
+ requirements: list[Requirement] = []
before = list(f)
- after: List[bytes] = []
+ after: list[bytes] = []
before_string = b''.join(before)
@@ -130,7 +130,7 @@ def fix_requirements(f: IO[bytes]) -> int:
return FAIL
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/sort_simple_yaml.py b/pre_commit_hooks/sort_simple_yaml.py
index 39f683e..116b5c1 100644
--- a/pre_commit_hooks/sort_simple_yaml.py
+++ b/pre_commit_hooks/sort_simple_yaml.py
@@ -17,16 +17,16 @@ We assume a strict subset of YAML that looks like:
In other words, we don't sort deeper than the top layer, and might corrupt
complicated YAML files.
"""
+from __future__ import annotations
+
import argparse
-from typing import List
-from typing import Optional
from typing import Sequence
QUOTES = ["'", '"']
-def sort(lines: List[str]) -> List[str]:
+def sort(lines: list[str]) -> list[str]:
"""Sort a YAML file in alphabetical order, keeping blocks together.
:param lines: array of strings (without newlines)
@@ -44,7 +44,7 @@ def sort(lines: List[str]) -> List[str]:
return new_lines
-def parse_block(lines: List[str], header: bool = False) -> List[str]:
+def parse_block(lines: list[str], header: bool = False) -> list[str]:
"""Parse and return a single block, popping off the start of `lines`.
If parsing a header block, we stop after we reach a line that is not a
@@ -60,7 +60,7 @@ def parse_block(lines: List[str], header: bool = False) -> List[str]:
return block_lines
-def parse_blocks(lines: List[str]) -> List[List[str]]:
+def parse_blocks(lines: list[str]) -> list[list[str]]:
"""Parse and return all possible blocks, popping off the start of `lines`.
:param lines: list of lines
@@ -77,7 +77,7 @@ def parse_blocks(lines: List[str]) -> List[List[str]]:
return blocks
-def first_key(lines: List[str]) -> str:
+def first_key(lines: list[str]) -> str:
"""Returns a string representing the sort key of a block.
The sort key is the first YAML key we encounter, ignoring comments, and
@@ -99,7 +99,7 @@ def first_key(lines: List[str]) -> str:
return '' # not actually reached in reality
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/string_fixer.py b/pre_commit_hooks/string_fixer.py
index a08a5f7..0ef9bc7 100644
--- a/pre_commit_hooks/string_fixer.py
+++ b/pre_commit_hooks/string_fixer.py
@@ -1,9 +1,9 @@
+from __future__ import annotations
+
import argparse
import io
import re
import tokenize
-from typing import List
-from typing import Optional
from typing import Sequence
START_QUOTE_RE = re.compile('^[a-zA-Z]*"')
@@ -24,7 +24,7 @@ def handle_match(token_text: str) -> str:
return token_text
-def get_line_offsets_by_line_no(src: str) -> List[int]:
+def get_line_offsets_by_line_no(src: str) -> list[int]:
# Padded so we can index with line number
offsets = [-1, 0]
for line in src.splitlines(True):
@@ -60,7 +60,7 @@ def fix_strings(filename: str) -> int:
return 0
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/tests_should_end_in_test.py b/pre_commit_hooks/tests_should_end_in_test.py
index bffb0c4..e7842af 100644
--- a/pre_commit_hooks/tests_should_end_in_test.py
+++ b/pre_commit_hooks/tests_should_end_in_test.py
@@ -1,30 +1,50 @@
+from __future__ import annotations
+
import argparse
import os.path
import re
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
- parser.add_argument(
- '--django', default=False, action='store_true',
- help='Use Django-style test naming pattern (test*.py)',
+ mutex = parser.add_mutually_exclusive_group()
+ mutex.add_argument(
+ '--pytest',
+ dest='pattern',
+ action='store_const',
+ const=r'.*_test\.py',
+ default=r'.*_test\.py',
+ help='(the default) ensure tests match %(const)s',
+ )
+ mutex.add_argument(
+ '--pytest-test-first',
+ dest='pattern',
+ action='store_const',
+ const=r'test_.*\.py',
+ help='ensure tests match %(const)s',
+ )
+ mutex.add_argument(
+ '--django', '--unittest',
+ dest='pattern',
+ action='store_const',
+ const=r'test.*\.py',
+ help='ensure tests match %(const)s',
)
args = parser.parse_args(argv)
retcode = 0
- test_name_pattern = r'test.*\.py' if args.django else r'.*_test\.py'
+ reg = re.compile(args.pattern)
for filename in args.filenames:
base = os.path.basename(filename)
if (
- not re.match(test_name_pattern, base) and
+ not reg.fullmatch(base) and
not base == '__init__.py' and
not base == 'conftest.py'
):
retcode = 1
- print(f'{filename} does not match pattern "{test_name_pattern}"')
+ print(f'{filename} does not match pattern "{args.pattern}"')
return retcode
diff --git a/pre_commit_hooks/trailing_whitespace_fixer.py b/pre_commit_hooks/trailing_whitespace_fixer.py
index 82faa2d..84f5067 100644
--- a/pre_commit_hooks/trailing_whitespace_fixer.py
+++ b/pre_commit_hooks/trailing_whitespace_fixer.py
@@ -1,13 +1,14 @@
+from __future__ import annotations
+
import argparse
import os
-from typing import Optional
from typing import Sequence
def _fix_file(
filename: str,
is_markdown: bool,
- chars: Optional[bytes],
+ chars: bytes | None,
) -> bool:
with open(filename, mode='rb') as file_processed:
lines = file_processed.readlines()
@@ -24,7 +25,7 @@ def _fix_file(
def _process_line(
line: bytes,
is_markdown: bool,
- chars: Optional[bytes],
+ chars: bytes | None,
) -> bytes:
if line[-2:] == b'\r\n':
eol = b'\r\n'
@@ -40,7 +41,7 @@ def _process_line(
return line.rstrip(chars) + eol
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'--no-markdown-linebreak-ext',
diff --git a/pre_commit_hooks/util.py b/pre_commit_hooks/util.py
index 402e33e..d6c90ae 100644
--- a/pre_commit_hooks/util.py
+++ b/pre_commit_hooks/util.py
@@ -1,20 +1,19 @@
+from __future__ import annotations
+
import subprocess
from typing import Any
-from typing import List
-from typing import Optional
-from typing import Set
class CalledProcessError(RuntimeError):
pass
-def added_files() -> Set[str]:
+def added_files() -> set[str]:
cmd = ('git', 'diff', '--staged', '--name-only', '--diff-filter=A')
return set(cmd_output(*cmd).splitlines())
-def cmd_output(*cmd: str, retcode: Optional[int] = 0, **kwargs: Any) -> str:
+def cmd_output(*cmd: str, retcode: int | None = 0, **kwargs: Any) -> str:
kwargs.setdefault('stdout', subprocess.PIPE)
kwargs.setdefault('stderr', subprocess.PIPE)
proc = subprocess.Popen(cmd, **kwargs)
@@ -25,7 +24,7 @@ def cmd_output(*cmd: str, retcode: Optional[int] = 0, **kwargs: Any) -> str:
return stdout
-def zsplit(s: str) -> List[str]:
+def zsplit(s: str) -> list[str]:
s = s.strip('\0')
if s:
return s.split('\0')