summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoris Roovers <joris.roovers@gmail.com>2023-06-15 17:05:33 +0200
committerGitHub <noreply@github.com>2023-06-15 17:05:33 +0200
commit345414171baea56c5b2b8290f17a2a13a685274c (patch)
tree23a76939aa1c8697a56e745158a46d7044410419
parent70932a147901c0abc5fc32e832e50ad9a3d16988 (diff)
Additional type annotations for functions (#507)
Adds a few more type annotations to functions, a lot more to go.
-rw-r--r--gitlint-core/gitlint/display.py15
-rw-r--r--gitlint-core/gitlint/git.py18
-rw-r--r--gitlint-core/gitlint/hooks.py3
-rw-r--r--gitlint-core/gitlint/lint.py5
-rw-r--r--gitlint-core/gitlint/options.py2
-rw-r--r--gitlint-core/gitlint/rule_finder.py9
-rw-r--r--gitlint-core/gitlint/rules.py31
-rw-r--r--gitlint-core/gitlint/shell.py9
-rw-r--r--gitlint-core/gitlint/tests/base.py7
-rw-r--r--pyproject.toml3
10 files changed, 57 insertions, 45 deletions
diff --git a/gitlint-core/gitlint/display.py b/gitlint-core/gitlint/display.py
index 834bca0..ac59e97 100644
--- a/gitlint-core/gitlint/display.py
+++ b/gitlint-core/gitlint/display.py
@@ -1,5 +1,6 @@
from dataclasses import dataclass
from sys import stderr, stdout
+from typing import TextIO
from gitlint.config import LintConfig
@@ -10,7 +11,7 @@ class Display:
config: LintConfig
- def _output(self, message, verbosity, exact, stream):
+ def _output(self, message: str, verbosity: int, exact: bool, stream: TextIO) -> None:
"""Output a message if the config's verbosity is >= to the given verbosity. If exact == True, the message
will only be outputted if the given verbosity exactly matches the config's verbosity."""
if exact:
@@ -20,20 +21,20 @@ class Display:
if self.config.verbosity >= verbosity:
stream.write(message + "\n")
- def v(self, message, exact=False):
+ def v(self, message: str, exact: bool = False) -> None:
self._output(message, 1, exact, stdout)
- def vv(self, message, exact=False):
+ def vv(self, message: str, exact: bool = False) -> None:
self._output(message, 2, exact, stdout)
- def vvv(self, message, exact=False):
+ def vvv(self, message: str, exact: bool = False) -> None:
self._output(message, 3, exact, stdout)
- def e(self, message, exact=False):
+ def e(self, message: str, exact: bool = False) -> None:
self._output(message, 1, exact, stderr)
- def ee(self, message, exact=False):
+ def ee(self, message: str, exact: bool = False) -> None:
self._output(message, 2, exact, stderr)
- def eee(self, message, exact=False):
+ def eee(self, message: str, exact: bool = False) -> None:
self._output(message, 3, exact, stderr)
diff --git a/gitlint-core/gitlint/git.py b/gitlint-core/gitlint/git.py
index 9c08d2d..8ec7a8a 100644
--- a/gitlint-core/gitlint/git.py
+++ b/gitlint-core/gitlint/git.py
@@ -3,7 +3,7 @@ import os
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
-from typing import Dict, List, Optional
+from typing import Any, Dict, List, Optional, Union
import arrow
@@ -34,13 +34,13 @@ class GitNotInstalledError(GitContextError):
class GitExitCodeError(GitContextError):
- def __init__(self, command, stderr):
+ def __init__(self, command: str, stderr: str):
self.command = command
self.stderr = stderr
super().__init__(f"An error occurred while executing '{command}': {stderr}")
-def _git(*command_parts, **kwargs):
+def _git(*command_parts: str, **kwargs: Any) -> Union[str, sh.ShResult]:
"""Convenience function for running git commands. Automatically deals with exceptions and unicode."""
git_kwargs = {"_tty_out": False}
git_kwargs.update(kwargs)
@@ -57,13 +57,13 @@ def _git(*command_parts, **kwargs):
raise GitNotInstalledError from e
except ErrorReturnCode as e: # Something went wrong while executing the git command
error_msg = e.stderr.strip()
- error_msg_lower = error_msg.lower()
- if "_cwd" in git_kwargs and b"not a git repository" in error_msg_lower:
+ error_msg_lower = str(error_msg.lower())
+ if "_cwd" in git_kwargs and "not a git repository" in error_msg_lower:
raise GitContextError(f"{git_kwargs['_cwd']} is not a git repository.") from e
if (
- b"does not have any commits yet" in error_msg_lower
- or b"ambiguous argument 'head': unknown revision" in error_msg_lower
+ "does not have any commits yet" in error_msg_lower
+ or "ambiguous argument 'head': unknown revision" in error_msg_lower
):
msg = "Current branch has no commits. Gitlint requires at least one commit to function."
raise GitContextError(msg) from e
@@ -85,9 +85,9 @@ def git_commentchar(repository_path=None):
return commentchar.replace("\n", "")
-def git_hooks_dir(repository_path):
+def git_hooks_dir(repository_path: str) -> str:
"""Determine hooks directory for a given target dir"""
- hooks_dir = _git("rev-parse", "--git-path", "hooks", _cwd=repository_path)
+ hooks_dir = str(_git("rev-parse", "--git-path", "hooks", _cwd=repository_path))
hooks_dir = hooks_dir.replace("\n", "")
return os.path.realpath(os.path.join(repository_path, hooks_dir))
diff --git a/gitlint-core/gitlint/hooks.py b/gitlint-core/gitlint/hooks.py
index 98ded18..98a1702 100644
--- a/gitlint-core/gitlint/hooks.py
+++ b/gitlint-core/gitlint/hooks.py
@@ -2,6 +2,7 @@ import os
import shutil
import stat
+from gitlint.config import LintConfig
from gitlint.exception import GitlintError
from gitlint.git import git_hooks_dir
from gitlint.utils import FILE_ENCODING
@@ -19,7 +20,7 @@ class GitHookInstaller:
"""Utility class that provides methods for installing and uninstalling the gitlint commitmsg hook."""
@staticmethod
- def commit_msg_hook_path(lint_config):
+ def commit_msg_hook_path(lint_config: LintConfig) -> str:
return os.path.join(git_hooks_dir(lint_config.target), COMMIT_MSG_HOOK_DST_PATH)
@staticmethod
diff --git a/gitlint-core/gitlint/lint.py b/gitlint-core/gitlint/lint.py
index 8878669..fd6d84b 100644
--- a/gitlint-core/gitlint/lint.py
+++ b/gitlint-core/gitlint/lint.py
@@ -1,5 +1,6 @@
import logging
from dataclasses import dataclass, field
+from typing import List
from gitlint import rules as gitlint_rules
from gitlint.config import LintConfig
@@ -20,7 +21,7 @@ class GitLinter:
def __post_init__(self):
self.display = Display(self.config)
- def should_ignore_rule(self, rule):
+ def should_ignore_rule(self, rule: gitlint_rules.Rule) -> bool:
"""Determines whether a rule should be ignored based on the general list of commits to ignore"""
return rule.id in self.config.ignore or rule.name in self.config.ignore
@@ -115,7 +116,7 @@ class GitLinter:
violations.sort(key=lambda v: (-1 if v.line_nr is None else v.line_nr, v.rule_id))
return violations
- def print_violations(self, violations):
+ def print_violations(self, violations: List[gitlint_rules.RuleViolation]) -> None:
"""Print a given set of violations to the standard error output"""
for v in violations:
line_nr = v.line_nr if v.line_nr else "-"
diff --git a/gitlint-core/gitlint/options.py b/gitlint-core/gitlint/options.py
index b3be90e..11574ce 100644
--- a/gitlint-core/gitlint/options.py
+++ b/gitlint-core/gitlint/options.py
@@ -39,7 +39,7 @@ class RuleOption:
self.set(self.value)
@abstractmethod
- def set(self, value):
+ def set(self, value: Any) -> None:
"""Validates and sets the option's value"""
def __str__(self):
diff --git a/gitlint-core/gitlint/rule_finder.py b/gitlint-core/gitlint/rule_finder.py
index 810faa9..d37a693 100644
--- a/gitlint-core/gitlint/rule_finder.py
+++ b/gitlint-core/gitlint/rule_finder.py
@@ -3,11 +3,12 @@ import importlib
import inspect
import os
import sys
+from typing import List, Type
from gitlint import options, rules
-def find_rule_classes(extra_path):
+def find_rule_classes(extra_path: str) -> List[Type[rules.Rule]]:
"""
Searches a given directory or python module for rule classes. This is done by
adding the directory path to the python path, importing the modules and then finding
@@ -48,7 +49,7 @@ def find_rule_classes(extra_path):
sys.path.append(directory)
# Find all the rule classes in the found python files
- rule_classes = []
+ rule_classes: List[Type[rules.Rule]] = []
for module in modules:
# Import the module
try:
@@ -78,7 +79,9 @@ def find_rule_classes(extra_path):
return rule_classes
-def assert_valid_rule_class(clazz, rule_type="User-defined"): # noqa: PLR0912 (too many branches)
+def assert_valid_rule_class( # noqa: PLR0912 (too many branches)
+ clazz: Type[rules.Rule], rule_type: str = "User-defined"
+) -> None:
"""
Asserts that a given rule clazz is valid by checking a number of its properties:
- Rules must extend from LineRule, CommitRule or ConfigurationRule
diff --git a/gitlint-core/gitlint/rules.py b/gitlint-core/gitlint/rules.py
index 88cf162..f49cb1c 100644
--- a/gitlint-core/gitlint/rules.py
+++ b/gitlint-core/gitlint/rules.py
@@ -6,6 +6,7 @@ from typing import ClassVar, Dict, List, Optional, Type
from gitlint.deprecation import Deprecation
from gitlint.exception import GitlintError
+from gitlint.git import GitCommit
from gitlint.options import (
BoolOption,
IntOption,
@@ -58,6 +59,20 @@ class Rule:
return f"{self.id} {self.name}" # pragma: no cover
+@dataclass
+class RuleViolation:
+ """Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class
+ to indicate how and where the rule was broken."""
+
+ rule_id: str
+ message: str
+ content: Optional[str] = None
+ line_nr: Optional[int] = None
+
+ def __str__(self):
+ return f'{self.line_nr}: {self.rule_id} {self.message}: "{self.content}"'
+
+
class ConfigurationRule(Rule):
"""Class representing rules that can dynamically change the configuration of gitlint during runtime."""
@@ -84,20 +99,6 @@ class CommitMessageBody(LineRuleTarget):
"""Target class used for rules that apply to a commit message body"""
-@dataclass
-class RuleViolation:
- """Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class
- to indicate how and where the rule was broken."""
-
- rule_id: str
- message: str
- content: Optional[str] = None
- line_nr: Optional[int] = None
-
- def __str__(self):
- return f'{self.line_nr}: {self.rule_id} {self.message}: "{self.content}"'
-
-
class UserRuleError(GitlintError):
"""Error used to indicate that an error occurred while trying to load a user rule"""
@@ -108,7 +109,7 @@ class MaxLineLength(LineRule):
options_spec = [IntOption("line-length", 80, "Max line length")]
violation_message = "Line exceeds max length ({0}>{1})"
- def validate(self, line, _commit):
+ def validate(self, line: str, _commit: GitCommit) -> Optional[List[RuleViolation]]:
max_length = self.options["line-length"].value
if len(line) > max_length:
return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]
diff --git a/gitlint-core/gitlint/shell.py b/gitlint-core/gitlint/shell.py
index 95a91df..d0793da 100644
--- a/gitlint-core/gitlint/shell.py
+++ b/gitlint-core/gitlint/shell.py
@@ -5,12 +5,13 @@ We still keep the `sh` API and semantics so the rest of the gitlint codebase doe
import subprocess
from dataclasses import dataclass
+from typing import Any
from gitlint.utils import TERMINAL_ENCODING
-def shell(cmd):
- """Convenience function that opens a given command in a shell. Does not use 'sh' library."""
+def shell(cmd: str) -> None:
+ """Convenience function that opens a given command in a shell."""
with subprocess.Popen(cmd, shell=True) as p:
p.communicate()
@@ -36,7 +37,7 @@ class ErrorReturnCode(ShResult, Exception):
"""ShResult subclass for unexpected results (acts as an exception)."""
-def git(*command_parts, **kwargs):
+def git(*command_parts: str, **kwargs: Any) -> ShResult:
"""Git shell wrapper.
Implemented as separate function here, so we can do a 'sh' style imports:
`from shell import git`
@@ -45,7 +46,7 @@ def git(*command_parts, **kwargs):
return _exec(*args, **kwargs)
-def _exec(*args, **kwargs):
+def _exec(*args: str, **kwargs: Any) -> ShResult:
pipe = subprocess.PIPE
popen_kwargs = {"stdout": pipe, "stderr": pipe, "shell": kwargs.get("_tty_out", False)}
if "_cwd" in kwargs:
diff --git a/gitlint-core/gitlint/tests/base.py b/gitlint-core/gitlint/tests/base.py
index a70e921..602e39d 100644
--- a/gitlint-core/gitlint/tests/base.py
+++ b/gitlint-core/gitlint/tests/base.py
@@ -7,6 +7,7 @@ import shutil
import tempfile
import unittest
from pathlib import Path
+from typing import Any, Dict, Optional
from unittest.mock import patch
from gitlint.config import LintConfig
@@ -84,7 +85,7 @@ class BaseTestCase(unittest.TestCase):
shutil.rmtree(tmpdir)
@staticmethod
- def get_sample_path(filename=""):
+ def get_sample_path(filename: str = "") -> str:
# Don't join up empty files names because this will add a trailing slash
if filename == "":
return BaseTestCase.SAMPLES_DIR
@@ -92,7 +93,7 @@ class BaseTestCase(unittest.TestCase):
return os.path.join(BaseTestCase.SAMPLES_DIR, filename)
@staticmethod
- def get_sample(filename=""):
+ def get_sample(filename: str = "") -> str:
"""Read and return the contents of a file in gitlint/tests/samples"""
sample_path = BaseTestCase.get_sample_path(filename)
return Path(sample_path).read_text(encoding=FILE_ENCODING)
@@ -105,7 +106,7 @@ class BaseTestCase(unittest.TestCase):
return patched_module
@staticmethod
- def get_expected(filename="", variable_dict=None):
+ def get_expected(filename: str = "", variable_dict: Optional[Dict[str, Any]] = None) -> str:
"""Utility method to read an expected file from gitlint/tests/expected and return it as a string.
Optionally replace template variables specified by variable_dict."""
expected_path = os.path.join(BaseTestCase.EXPECTED_DIR, filename)
diff --git a/pyproject.toml b/pyproject.toml
index 14db408..d718a33 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -244,6 +244,9 @@ disallow_incomplete_defs = true
# disallow_untyped_defs = true
# no_implicit_reexport = true
+# Allow not explicitly returning when the function return type includes None
+no_warn_no_return = true
+
exclude = [
"hatch_build.py",
"tools/*",