Skip to content

Commit 3454141

Browse files
authored
Additional type annotations for functions (#507)
Adds a few more type annotations to functions, a lot more to go.
1 parent 70932a1 commit 3454141

File tree

10 files changed

+57
-45
lines changed

10 files changed

+57
-45
lines changed

gitlint-core/gitlint/display.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from dataclasses import dataclass
22
from sys import stderr, stdout
3+
from typing import TextIO
34

45
from gitlint.config import LintConfig
56

@@ -10,7 +11,7 @@ class Display:
1011

1112
config: LintConfig
1213

13-
def _output(self, message, verbosity, exact, stream):
14+
def _output(self, message: str, verbosity: int, exact: bool, stream: TextIO) -> None:
1415
"""Output a message if the config's verbosity is >= to the given verbosity. If exact == True, the message
1516
will only be outputted if the given verbosity exactly matches the config's verbosity."""
1617
if exact:
@@ -20,20 +21,20 @@ def _output(self, message, verbosity, exact, stream):
2021
if self.config.verbosity >= verbosity:
2122
stream.write(message + "\n")
2223

23-
def v(self, message, exact=False):
24+
def v(self, message: str, exact: bool = False) -> None:
2425
self._output(message, 1, exact, stdout)
2526

26-
def vv(self, message, exact=False):
27+
def vv(self, message: str, exact: bool = False) -> None:
2728
self._output(message, 2, exact, stdout)
2829

29-
def vvv(self, message, exact=False):
30+
def vvv(self, message: str, exact: bool = False) -> None:
3031
self._output(message, 3, exact, stdout)
3132

32-
def e(self, message, exact=False):
33+
def e(self, message: str, exact: bool = False) -> None:
3334
self._output(message, 1, exact, stderr)
3435

35-
def ee(self, message, exact=False):
36+
def ee(self, message: str, exact: bool = False) -> None:
3637
self._output(message, 2, exact, stderr)
3738

38-
def eee(self, message, exact=False):
39+
def eee(self, message: str, exact: bool = False) -> None:
3940
self._output(message, 3, exact, stderr)

gitlint-core/gitlint/git.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from dataclasses import dataclass, field
44
from datetime import datetime
55
from pathlib import Path
6-
from typing import Dict, List, Optional
6+
from typing import Any, Dict, List, Optional, Union
77

88
import arrow
99

@@ -34,13 +34,13 @@ def __init__(self):
3434

3535

3636
class GitExitCodeError(GitContextError):
37-
def __init__(self, command, stderr):
37+
def __init__(self, command: str, stderr: str):
3838
self.command = command
3939
self.stderr = stderr
4040
super().__init__(f"An error occurred while executing '{command}': {stderr}")
4141

4242

43-
def _git(*command_parts, **kwargs):
43+
def _git(*command_parts: str, **kwargs: Any) -> Union[str, sh.ShResult]:
4444
"""Convenience function for running git commands. Automatically deals with exceptions and unicode."""
4545
git_kwargs = {"_tty_out": False}
4646
git_kwargs.update(kwargs)
@@ -57,13 +57,13 @@ def _git(*command_parts, **kwargs):
5757
raise GitNotInstalledError from e
5858
except ErrorReturnCode as e: # Something went wrong while executing the git command
5959
error_msg = e.stderr.strip()
60-
error_msg_lower = error_msg.lower()
61-
if "_cwd" in git_kwargs and b"not a git repository" in error_msg_lower:
60+
error_msg_lower = str(error_msg.lower())
61+
if "_cwd" in git_kwargs and "not a git repository" in error_msg_lower:
6262
raise GitContextError(f"{git_kwargs['_cwd']} is not a git repository.") from e
6363

6464
if (
65-
b"does not have any commits yet" in error_msg_lower
66-
or b"ambiguous argument 'head': unknown revision" in error_msg_lower
65+
"does not have any commits yet" in error_msg_lower
66+
or "ambiguous argument 'head': unknown revision" in error_msg_lower
6767
):
6868
msg = "Current branch has no commits. Gitlint requires at least one commit to function."
6969
raise GitContextError(msg) from e
@@ -85,9 +85,9 @@ def git_commentchar(repository_path=None):
8585
return commentchar.replace("\n", "")
8686

8787

88-
def git_hooks_dir(repository_path):
88+
def git_hooks_dir(repository_path: str) -> str:
8989
"""Determine hooks directory for a given target dir"""
90-
hooks_dir = _git("rev-parse", "--git-path", "hooks", _cwd=repository_path)
90+
hooks_dir = str(_git("rev-parse", "--git-path", "hooks", _cwd=repository_path))
9191
hooks_dir = hooks_dir.replace("\n", "")
9292
return os.path.realpath(os.path.join(repository_path, hooks_dir))
9393

gitlint-core/gitlint/hooks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import shutil
33
import stat
44

5+
from gitlint.config import LintConfig
56
from gitlint.exception import GitlintError
67
from gitlint.git import git_hooks_dir
78
from gitlint.utils import FILE_ENCODING
@@ -19,7 +20,7 @@ class GitHookInstaller:
1920
"""Utility class that provides methods for installing and uninstalling the gitlint commitmsg hook."""
2021

2122
@staticmethod
22-
def commit_msg_hook_path(lint_config):
23+
def commit_msg_hook_path(lint_config: LintConfig) -> str:
2324
return os.path.join(git_hooks_dir(lint_config.target), COMMIT_MSG_HOOK_DST_PATH)
2425

2526
@staticmethod

gitlint-core/gitlint/lint.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
from dataclasses import dataclass, field
3+
from typing import List
34

45
from gitlint import rules as gitlint_rules
56
from gitlint.config import LintConfig
@@ -20,7 +21,7 @@ class GitLinter:
2021
def __post_init__(self):
2122
self.display = Display(self.config)
2223

23-
def should_ignore_rule(self, rule):
24+
def should_ignore_rule(self, rule: gitlint_rules.Rule) -> bool:
2425
"""Determines whether a rule should be ignored based on the general list of commits to ignore"""
2526
return rule.id in self.config.ignore or rule.name in self.config.ignore
2627

@@ -115,7 +116,7 @@ def lint(self, commit):
115116
violations.sort(key=lambda v: (-1 if v.line_nr is None else v.line_nr, v.rule_id))
116117
return violations
117118

118-
def print_violations(self, violations):
119+
def print_violations(self, violations: List[gitlint_rules.RuleViolation]) -> None:
119120
"""Print a given set of violations to the standard error output"""
120121
for v in violations:
121122
line_nr = v.line_nr if v.line_nr else "-"

gitlint-core/gitlint/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def __post_init__(self):
3939
self.set(self.value)
4040

4141
@abstractmethod
42-
def set(self, value):
42+
def set(self, value: Any) -> None:
4343
"""Validates and sets the option's value"""
4444

4545
def __str__(self):

gitlint-core/gitlint/rule_finder.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import inspect
44
import os
55
import sys
6+
from typing import List, Type
67

78
from gitlint import options, rules
89

910

10-
def find_rule_classes(extra_path):
11+
def find_rule_classes(extra_path: str) -> List[Type[rules.Rule]]:
1112
"""
1213
Searches a given directory or python module for rule classes. This is done by
1314
adding the directory path to the python path, importing the modules and then finding
@@ -48,7 +49,7 @@ def find_rule_classes(extra_path):
4849
sys.path.append(directory)
4950

5051
# Find all the rule classes in the found python files
51-
rule_classes = []
52+
rule_classes: List[Type[rules.Rule]] = []
5253
for module in modules:
5354
# Import the module
5455
try:
@@ -78,7 +79,9 @@ def find_rule_classes(extra_path):
7879
return rule_classes
7980

8081

81-
def assert_valid_rule_class(clazz, rule_type="User-defined"): # noqa: PLR0912 (too many branches)
82+
def assert_valid_rule_class( # noqa: PLR0912 (too many branches)
83+
clazz: Type[rules.Rule], rule_type: str = "User-defined"
84+
) -> None:
8285
"""
8386
Asserts that a given rule clazz is valid by checking a number of its properties:
8487
- Rules must extend from LineRule, CommitRule or ConfigurationRule

gitlint-core/gitlint/rules.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from gitlint.deprecation import Deprecation
88
from gitlint.exception import GitlintError
9+
from gitlint.git import GitCommit
910
from gitlint.options import (
1011
BoolOption,
1112
IntOption,
@@ -58,6 +59,20 @@ def __str__(self):
5859
return f"{self.id} {self.name}" # pragma: no cover
5960

6061

62+
@dataclass
63+
class RuleViolation:
64+
"""Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class
65+
to indicate how and where the rule was broken."""
66+
67+
rule_id: str
68+
message: str
69+
content: Optional[str] = None
70+
line_nr: Optional[int] = None
71+
72+
def __str__(self):
73+
return f'{self.line_nr}: {self.rule_id} {self.message}: "{self.content}"'
74+
75+
6176
class ConfigurationRule(Rule):
6277
"""Class representing rules that can dynamically change the configuration of gitlint during runtime."""
6378

@@ -84,20 +99,6 @@ class CommitMessageBody(LineRuleTarget):
8499
"""Target class used for rules that apply to a commit message body"""
85100

86101

87-
@dataclass
88-
class RuleViolation:
89-
"""Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class
90-
to indicate how and where the rule was broken."""
91-
92-
rule_id: str
93-
message: str
94-
content: Optional[str] = None
95-
line_nr: Optional[int] = None
96-
97-
def __str__(self):
98-
return f'{self.line_nr}: {self.rule_id} {self.message}: "{self.content}"'
99-
100-
101102
class UserRuleError(GitlintError):
102103
"""Error used to indicate that an error occurred while trying to load a user rule"""
103104

@@ -108,7 +109,7 @@ class MaxLineLength(LineRule):
108109
options_spec = [IntOption("line-length", 80, "Max line length")]
109110
violation_message = "Line exceeds max length ({0}>{1})"
110111

111-
def validate(self, line, _commit):
112+
def validate(self, line: str, _commit: GitCommit) -> Optional[List[RuleViolation]]:
112113
max_length = self.options["line-length"].value
113114
if len(line) > max_length:
114115
return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]

gitlint-core/gitlint/shell.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55

66
import subprocess
77
from dataclasses import dataclass
8+
from typing import Any
89

910
from gitlint.utils import TERMINAL_ENCODING
1011

1112

12-
def shell(cmd):
13-
"""Convenience function that opens a given command in a shell. Does not use 'sh' library."""
13+
def shell(cmd: str) -> None:
14+
"""Convenience function that opens a given command in a shell."""
1415
with subprocess.Popen(cmd, shell=True) as p:
1516
p.communicate()
1617

@@ -36,7 +37,7 @@ class ErrorReturnCode(ShResult, Exception):
3637
"""ShResult subclass for unexpected results (acts as an exception)."""
3738

3839

39-
def git(*command_parts, **kwargs):
40+
def git(*command_parts: str, **kwargs: Any) -> ShResult:
4041
"""Git shell wrapper.
4142
Implemented as separate function here, so we can do a 'sh' style imports:
4243
`from shell import git`
@@ -45,7 +46,7 @@ def git(*command_parts, **kwargs):
4546
return _exec(*args, **kwargs)
4647

4748

48-
def _exec(*args, **kwargs):
49+
def _exec(*args: str, **kwargs: Any) -> ShResult:
4950
pipe = subprocess.PIPE
5051
popen_kwargs = {"stdout": pipe, "stderr": pipe, "shell": kwargs.get("_tty_out", False)}
5152
if "_cwd" in kwargs:

gitlint-core/gitlint/tests/base.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import tempfile
88
import unittest
99
from pathlib import Path
10+
from typing import Any, Dict, Optional
1011
from unittest.mock import patch
1112

1213
from gitlint.config import LintConfig
@@ -84,15 +85,15 @@ def tempdir():
8485
shutil.rmtree(tmpdir)
8586

8687
@staticmethod
87-
def get_sample_path(filename=""):
88+
def get_sample_path(filename: str = "") -> str:
8889
# Don't join up empty files names because this will add a trailing slash
8990
if filename == "":
9091
return BaseTestCase.SAMPLES_DIR
9192

9293
return os.path.join(BaseTestCase.SAMPLES_DIR, filename)
9394

9495
@staticmethod
95-
def get_sample(filename=""):
96+
def get_sample(filename: str = "") -> str:
9697
"""Read and return the contents of a file in gitlint/tests/samples"""
9798
sample_path = BaseTestCase.get_sample_path(filename)
9899
return Path(sample_path).read_text(encoding=FILE_ENCODING)
@@ -105,7 +106,7 @@ def patch_input(side_effect):
105106
return patched_module
106107

107108
@staticmethod
108-
def get_expected(filename="", variable_dict=None):
109+
def get_expected(filename: str = "", variable_dict: Optional[Dict[str, Any]] = None) -> str:
109110
"""Utility method to read an expected file from gitlint/tests/expected and return it as a string.
110111
Optionally replace template variables specified by variable_dict."""
111112
expected_path = os.path.join(BaseTestCase.EXPECTED_DIR, filename)

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ disallow_incomplete_defs = true
244244
# disallow_untyped_defs = true
245245
# no_implicit_reexport = true
246246

247+
# Allow not explicitly returning when the function return type includes None
248+
no_warn_no_return = true
249+
247250
exclude = [
248251
"hatch_build.py",
249252
"tools/*",

0 commit comments

Comments
 (0)