diff options
author | Sebastian Thiel <sebastian.thiel@icloud.com> | 2021-06-26 10:09:53 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-26 10:09:53 +0800 |
commit | 2d2ff037f9f7a9ae33e5f4f6bdb75b669a1af19a (patch) | |
tree | 5f4fd00ad13fa5455dc876ab9cb9cc4f9b66bdfc /git/util.py | |
parent | 703280b8c3df6f9b1a5cbe0997b717edbcaa8979 (diff) | |
parent | 5d7b8ba9f2e9298496232e4ae66bd904a1d71001 (diff) | |
download | gitpython-2d2ff037f9f7a9ae33e5f4f6bdb75b669a1af19a.tar.gz |
Merge pull request #1279 from Yobmod/main
Finish typing object, improve verious other types.
Diffstat (limited to 'git/util.py')
-rw-r--r-- | git/util.py | 96 |
1 files changed, 77 insertions, 19 deletions
diff --git a/git/util.py b/git/util.py index 516c315c..eccaa74e 100644 --- a/git/util.py +++ b/git/util.py @@ -18,18 +18,21 @@ from sys import maxsize import time from unittest import SkipTest from urllib.parse import urlsplit, urlunsplit +import warnings # typing --------------------------------------------------------- from typing import (Any, AnyStr, BinaryIO, Callable, Dict, Generator, IO, Iterator, List, - Optional, Pattern, Sequence, Tuple, Union, cast, TYPE_CHECKING, overload) + Optional, Pattern, Sequence, Tuple, TypeVar, Union, cast, TYPE_CHECKING, overload) import pathlib if TYPE_CHECKING: from git.remote import Remote from git.repo.base import Repo -from .types import PathLike, TBD, Literal, SupportsIndex + from git.config import GitConfigParser, SectionConstraint + +from .types import PathLike, Literal, SupportsIndex, HSH_TD, Files_TD # --------------------------------------------------------------------- @@ -81,7 +84,7 @@ def unbare_repo(func: Callable) -> Callable: encounter a bare repository""" @wraps(func) - def wrapper(self: 'Remote', *args: Any, **kwargs: Any) -> TBD: + def wrapper(self: 'Remote', *args: Any, **kwargs: Any) -> Callable: if self.repo.bare: raise InvalidGitRepositoryError("Method '%s' cannot operate on bare repositories" % func.__name__) # END bare method @@ -107,7 +110,7 @@ def rmtree(path: PathLike) -> None: :note: we use shutil rmtree but adjust its behaviour to see whether files that couldn't be deleted are read-only. Windows will not remove them in that case""" - def onerror(func: Callable, path: PathLike, exc_info: TBD) -> None: + def onerror(func: Callable, path: PathLike, exc_info: str) -> None: # Is the error an access error ? os.chmod(path, stat.S_IWUSR) @@ -447,7 +450,7 @@ class RemoteProgress(object): re_op_relative = re.compile(r"(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)") def __init__(self) -> None: - self._seen_ops = [] # type: List[TBD] + self._seen_ops = [] # type: List[int] self._cur_line = None # type: Optional[str] self.error_lines = [] # type: List[str] self.other_lines = [] # type: List[str] @@ -668,7 +671,8 @@ class Actor(object): # END handle name/email matching @classmethod - def _main_actor(cls, env_name: str, env_email: str, config_reader: Optional[TBD] = None) -> 'Actor': + def _main_actor(cls, env_name: str, env_email: str, + config_reader: Union[None, 'GitConfigParser', 'SectionConstraint'] = None) -> 'Actor': actor = Actor('', '') user_id = None # We use this to avoid multiple calls to getpass.getuser() @@ -697,7 +701,7 @@ class Actor(object): return actor @classmethod - def committer(cls, config_reader: Optional[TBD] = None) -> 'Actor': + def committer(cls, config_reader: Union[None, 'GitConfigParser', 'SectionConstraint'] = None) -> 'Actor': """ :return: Actor instance corresponding to the configured committer. It behaves similar to the git implementation, such that the environment will override @@ -708,7 +712,7 @@ class Actor(object): return cls._main_actor(cls.env_committer_name, cls.env_committer_email, config_reader) @classmethod - def author(cls, config_reader: Optional[TBD] = None) -> 'Actor': + def author(cls, config_reader: Union[None, 'GitConfigParser', 'SectionConstraint'] = None) -> 'Actor': """Same as committer(), but defines the main author. It may be specified in the environment, but defaults to the committer""" return cls._main_actor(cls.env_author_name, cls.env_author_email, config_reader) @@ -742,7 +746,9 @@ class Stats(object): files = number of changed files as int""" __slots__ = ("total", "files") - def __init__(self, total: Dict[str, Dict[str, int]], files: Dict[str, Dict[str, int]]): + from git.types import Total_TD, Files_TD + + def __init__(self, total: Total_TD, files: Dict[PathLike, Files_TD]): self.total = total self.files = files @@ -751,9 +757,13 @@ class Stats(object): """Create a Stat object from output retrieved by git-diff. :return: git.Stat""" - hsh = {'total': {'insertions': 0, 'deletions': 0, 'lines': 0, 'files': 0}, - 'files': {} - } # type: Dict[str, Dict[str, TBD]] ## need typeddict or refactor for mypy + + hsh: HSH_TD = {'total': {'insertions': 0, + 'deletions': 0, + 'lines': 0, + 'files': 0}, + 'files': {} + } for line in text.splitlines(): (raw_insertions, raw_deletions, filename) = line.split("\t") insertions = raw_insertions != '-' and int(raw_insertions) or 0 @@ -762,9 +772,10 @@ class Stats(object): hsh['total']['deletions'] += deletions hsh['total']['lines'] += insertions + deletions hsh['total']['files'] += 1 - hsh['files'][filename.strip()] = {'insertions': insertions, - 'deletions': deletions, - 'lines': insertions + deletions} + files_dict: Files_TD = {'insertions': insertions, + 'deletions': deletions, + 'lines': insertions + deletions} + hsh['files'][filename.strip()] = files_dict return Stats(hsh['total'], hsh['files']) @@ -920,7 +931,10 @@ class BlockingLockFile(LockFile): # END endless loop -class IterableList(list): +T = TypeVar('T', bound='IterableObj') + + +class IterableList(List[T]): """ List of iterable objects allowing to query an object by id or by named index:: @@ -930,6 +944,9 @@ class IterableList(list): heads['master'] heads[0] + Iterable parent objects = [Commit, SubModule, Reference, FetchInfo, PushInfo] + Iterable via inheritance = [Head, TagReference, RemoteReference] + ] It requires an id_attribute name to be set which will be queried from its contained items to have a means for comparison. @@ -938,7 +955,7 @@ class IterableList(list): can be left out.""" __slots__ = ('_id_attr', '_prefix') - def __new__(cls, id_attr: str, prefix: str = '') -> 'IterableList': + def __new__(cls, id_attr: str, prefix: str = '') -> 'IterableList[IterableObj]': return super(IterableList, cls).__new__(cls) def __init__(self, id_attr: str, prefix: str = '') -> None: @@ -1007,16 +1024,29 @@ class IterableList(list): list.__delitem__(self, delindex) +class IterableClassWatcher(type): + def __init__(cls, name, bases, clsdict): + for base in bases: + if type(base) == IterableClassWatcher: + warnings.warn(f"GitPython Iterable subclassed by {name}. " + "Iterable is deprecated due to naming clash, " + "Use IterableObj instead \n", + DeprecationWarning, + stacklevel=2) + + class Iterable(object): """Defines an interface for iterable items which is to assure a uniform way to retrieve and iterate items within the git repository""" __slots__ = () _id_attribute_ = "attribute that most suitably identifies your instance" + __metaclass__ = IterableClassWatcher @classmethod - def list_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> 'IterableList': + def list_items(cls, repo, *args, **kwargs): """ + Deprecaated, use IterableObj instead. Find all items of this type - subclasses can specify args and kwargs differently. If no args are given, subclasses are obliged to return all items if no additional arguments arg given. @@ -1029,7 +1059,35 @@ class Iterable(object): return out_list @classmethod - def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Iterator[TBD]: + def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any): + # return typed to be compatible with subtypes e.g. Remote + """For more information about the arguments, see list_items + :return: iterator yielding Items""" + raise NotImplementedError("To be implemented by Subclass") + + +class IterableObj(): + """Defines an interface for iterable items which is to assure a uniform + way to retrieve and iterate items within the git repository""" + __slots__ = () + _id_attribute_ = "attribute that most suitably identifies your instance" + + @classmethod + def list_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> IterableList[T]: + """ + Find all items of this type - subclasses can specify args and kwargs differently. + If no args are given, subclasses are obliged to return all items if no additional + arguments arg given. + + :note: Favor the iter_items method as it will + + :return:list(Item,...) list of item instances""" + out_list: IterableList = IterableList(cls._id_attribute_) + out_list.extend(cls.iter_items(repo, *args, **kwargs)) + return out_list + + @classmethod + def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Iterator[T]: # return typed to be compatible with subtypes e.g. Remote """For more information about the arguments, see list_items :return: iterator yielding Items""" |