diff options
author | Joris Roovers <joris.roovers@gmail.com> | 2023-06-15 17:05:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-15 17:05:33 +0200 |
commit | 345414171baea56c5b2b8290f17a2a13a685274c (patch) | |
tree | 23a76939aa1c8697a56e745158a46d7044410419 | |
parent | 70932a147901c0abc5fc32e832e50ad9a3d16988 (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.py | 15 | ||||
-rw-r--r-- | gitlint-core/gitlint/git.py | 18 | ||||
-rw-r--r-- | gitlint-core/gitlint/hooks.py | 3 | ||||
-rw-r--r-- | gitlint-core/gitlint/lint.py | 5 | ||||
-rw-r--r-- | gitlint-core/gitlint/options.py | 2 | ||||
-rw-r--r-- | gitlint-core/gitlint/rule_finder.py | 9 | ||||
-rw-r--r-- | gitlint-core/gitlint/rules.py | 31 | ||||
-rw-r--r-- | gitlint-core/gitlint/shell.py | 9 | ||||
-rw-r--r-- | gitlint-core/gitlint/tests/base.py | 7 | ||||
-rw-r--r-- | pyproject.toml | 3 |
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/*", |