Skip to content

Commit

Permalink
Additional type annotations for functions (#507)
Browse files Browse the repository at this point in the history
Adds a few more type annotations to functions, a lot more to go.
  • Loading branch information
jorisroovers committed Jun 15, 2023
1 parent 70932a1 commit 3454141
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 45 deletions.
15 changes: 8 additions & 7 deletions gitlint-core/gitlint/display.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dataclasses import dataclass
from sys import stderr, stdout
from typing import TextIO

from gitlint.config import LintConfig

Expand All @@ -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:
Expand All @@ -20,20 +21,20 @@ def _output(self, message, verbosity, exact, stream):
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)
18 changes: 9 additions & 9 deletions gitlint-core/gitlint/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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

Expand Down Expand Up @@ -34,13 +34,13 @@ def __init__(self):


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)
Expand All @@ -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
Expand All @@ -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))

Expand Down
3 changes: 2 additions & 1 deletion gitlint-core/gitlint/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
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
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions gitlint-core/gitlint/lint.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand Down Expand Up @@ -115,7 +116,7 @@ def lint(self, commit):
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 "-"
Expand Down
2 changes: 1 addition & 1 deletion gitlint-core/gitlint/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def __post_init__(self):
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):
Expand Down
9 changes: 6 additions & 3 deletions gitlint-core/gitlint/rule_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
31 changes: 16 additions & 15 deletions gitlint-core/gitlint/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from gitlint.deprecation import Deprecation
from gitlint.exception import GitlintError
from gitlint.git import GitCommit
from gitlint.options import (
BoolOption,
IntOption,
Expand Down Expand Up @@ -58,6 +59,20 @@ def __str__(self):
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."""

Expand All @@ -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"""

Expand All @@ -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)]
Expand Down
9 changes: 5 additions & 4 deletions gitlint-core/gitlint/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

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()

Expand All @@ -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`
Expand All @@ -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:
Expand Down
7 changes: 4 additions & 3 deletions gitlint-core/gitlint/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import tempfile
import unittest
from pathlib import Path
from typing import Any, Dict, Optional
from unittest.mock import patch

from gitlint.config import LintConfig
Expand Down Expand Up @@ -84,15 +85,15 @@ def tempdir():
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

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)
Expand All @@ -105,7 +106,7 @@ def patch_input(side_effect):
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)
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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/*",
Expand Down

0 comments on commit 3454141

Please sign in to comment.