From 5b6fe83f4d817a3b73b44df16cfb4f96bd4d9904 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Wed, 23 Jun 2021 02:22:34 +0100 Subject: Update typing-extensions version in requirements.txt --- git/util.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index edbd5f1e..516c315c 100644 --- a/git/util.py +++ b/git/util.py @@ -29,7 +29,7 @@ import pathlib if TYPE_CHECKING: from git.remote import Remote from git.repo.base import Repo -from .types import PathLike, TBD, Literal +from .types import PathLike, TBD, Literal, SupportsIndex # --------------------------------------------------------------------- @@ -971,7 +971,10 @@ class IterableList(list): # END for each item return list.__getattribute__(self, attr) - def __getitem__(self, index: Union[int, slice, str]) -> Any: + def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> Any: + + assert isinstance(index, (int, str, slice)), "Index of IterableList should be an int or str" + if isinstance(index, int): return list.__getitem__(self, index) elif isinstance(index, slice): @@ -983,12 +986,13 @@ class IterableList(list): raise IndexError("No item found with id %r" % (self._prefix + index)) from e # END handle getattr - def __delitem__(self, index: Union[int, str, slice]) -> None: + def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> Any: + + assert isinstance(index, (int, str)), "Index of IterableList should be an int or str" delindex = cast(int, index) if not isinstance(index, int): delindex = -1 - assert not isinstance(index, slice) name = self._prefix + index for i, item in enumerate(self): if getattr(item, self._id_attr) == name: -- cgit v1.2.1 From c3903d8e03af5c1e01c1a96919b926c55f45052e Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 14:27:13 +0100 Subject: Make IterableList generic and update throughout --- git/util.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index 516c315c..5f184b7a 100644 --- a/git/util.py +++ b/git/util.py @@ -22,7 +22,7 @@ from urllib.parse import urlsplit, urlunsplit # 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 @@ -920,7 +920,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 +933,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 +944,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: @@ -1015,7 +1021,7 @@ class Iterable(object): _id_attribute_ = "attribute that most suitably identifies your instance" @classmethod - def list_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> 'IterableList': + def list_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> IterableList['IterableObj']: """ 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 @@ -1024,12 +1030,12 @@ class Iterable(object): :note: Favor the iter_items method as it will :return:list(Item,...) list of item instances""" - out_list = IterableList(cls._id_attribute_) + 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[TBD]: + def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Iterator: # return typed to be compatible with subtypes e.g. Remote """For more information about the arguments, see list_items :return: iterator yielding Items""" @@ -1038,6 +1044,10 @@ class Iterable(object): #} END classes +class IterableObj(Iterable): + pass + + class NullHandler(logging.Handler): def emit(self, record: object) -> None: pass -- cgit v1.2.1 From 3cef949913659584dd980f3de363dd830392bb68 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 15:32:25 +0100 Subject: Rename Iterable due to typing.Iterable. Add deprecation warning --- git/util.py | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index 5f184b7a..f72cd355 100644 --- a/git/util.py +++ b/git/util.py @@ -18,6 +18,7 @@ from sys import maxsize import time from unittest import SkipTest from urllib.parse import urlsplit, urlunsplit +import warnings # typing --------------------------------------------------------- @@ -1013,15 +1014,52 @@ class IterableList(List[T]): list.__delitem__(self, delindex) +class IterableClassWatcher(type): + def __init__(cls, name, bases, clsdict): + for base in bases: + if type(base) == cls: + warnings.warn("GitPython Iterable is deprecated due to naming clash. Use IterableObj instead", + DeprecationWarning) + super(IterableClassWatcher, cls).__init__(name, bases, clsdict) + + 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['IterableObj']: + def list_items(cls, repo, *args, **kwargs): + """ + 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(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): + # 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 @@ -1044,10 +1082,6 @@ class Iterable(object): #} END classes -class IterableObj(Iterable): - pass - - class NullHandler(logging.Handler): def emit(self, record: object) -> None: pass -- cgit v1.2.1 From ae9d56e0fdd4df335a9def66aa2ac96459ed6e5c Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 16:05:03 +0100 Subject: Make Iterable deprecation warning on subclassing --- git/util.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index f72cd355..78a60c9a 100644 --- a/git/util.py +++ b/git/util.py @@ -1017,10 +1017,12 @@ class IterableList(List[T]): class IterableClassWatcher(type): def __init__(cls, name, bases, clsdict): for base in bases: - if type(base) == cls: - warnings.warn("GitPython Iterable is deprecated due to naming clash. Use IterableObj instead", - DeprecationWarning) - super(IterableClassWatcher, cls).__init__(name, bases, clsdict) + 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): -- cgit v1.2.1 From 4f5d2fd68e784c2b2fd914a196c66960c7f48b49 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 16:30:32 +0100 Subject: update docstring --- git/util.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index 78a60c9a..79952be5 100644 --- a/git/util.py +++ b/git/util.py @@ -1036,11 +1036,13 @@ class Iterable(object): @classmethod 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. :note: Favor the iter_items method as it will + :return:list(Item,...) list of item instances""" out_list = IterableList(cls._id_attribute_) out_list.extend(cls.iter_items(repo, *args, **kwargs)) -- cgit v1.2.1 From d9f9027779931c3cdb04d570df5f01596539791b Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 17:09:51 +0100 Subject: update some TBDs to configparser --- git/util.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index 79952be5..245f45d1 100644 --- a/git/util.py +++ b/git/util.py @@ -30,6 +30,8 @@ import pathlib if TYPE_CHECKING: from git.remote import Remote from git.repo.base import Repo + from git.config import GitConfigParser, SectionConstraint + from .types import PathLike, TBD, Literal, SupportsIndex # --------------------------------------------------------------------- @@ -82,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 @@ -108,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) @@ -448,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] @@ -669,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() @@ -698,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 @@ -709,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) @@ -752,9 +755,14 @@ 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: Dict[str, Dict[str, Union[int, Dict[str, int]]]] + hsh: Dict[str, Dict[str, TBD]] = {'total': {'insertions': 0, + 'deletions': 0, + 'lines': 0, + 'files': 0}, + 'files': {} + } # need typeddict? for line in text.splitlines(): (raw_insertions, raw_deletions, filename) = line.split("\t") insertions = raw_insertions != '-' and int(raw_insertions) or 0 @@ -763,9 +771,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 = {'insertions': insertions, + 'deletions': deletions, + 'lines': insertions + deletions} + hsh['files'][filename.strip()] = files_dict return Stats(hsh['total'], hsh['files']) @@ -1077,7 +1086,7 @@ class IterableObj(): return out_list @classmethod - def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Iterator: + 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""" -- cgit v1.2.1 From affee359af09cf7971676263f59118de82e7e059 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 17:35:22 +0100 Subject: Add typedDict --- git/util.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index 245f45d1..0783918d 100644 --- a/git/util.py +++ b/git/util.py @@ -32,7 +32,7 @@ if TYPE_CHECKING: from git.repo.base import Repo from git.config import GitConfigParser, SectionConstraint -from .types import PathLike, TBD, Literal, SupportsIndex +from .types import PathLike, Literal, SupportsIndex, HSH_TD, Files_TD # --------------------------------------------------------------------- @@ -746,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[str, Files_TD]): self.total = total self.files = files @@ -756,13 +758,12 @@ class Stats(object): :return: git.Stat""" - # hsh: Dict[str, Dict[str, Union[int, Dict[str, int]]]] - hsh: Dict[str, Dict[str, TBD]] = {'total': {'insertions': 0, - 'deletions': 0, - 'lines': 0, - 'files': 0}, - 'files': {} - } # need typeddict? + 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 @@ -771,9 +772,9 @@ class Stats(object): hsh['total']['deletions'] += deletions hsh['total']['lines'] += insertions + deletions hsh['total']['files'] += 1 - files_dict = {'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']) -- cgit v1.2.1 From fe594eb345fbefaee3b82436183d6560991724cc Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 23:14:13 +0100 Subject: Add T_Tre_cache TypeVar --- git/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index 0783918d..bcc634ec 100644 --- a/git/util.py +++ b/git/util.py @@ -672,7 +672,7 @@ class Actor(object): @classmethod def _main_actor(cls, env_name: str, env_email: str, - config_reader: Union[None, GitConfigParser, SectionConstraint] = None) -> 'Actor': + config_reader: Union[None, 'GitConfigParser', 'SectionConstraint'] = None) -> 'Actor': actor = Actor('', '') user_id = None # We use this to avoid multiple calls to getpass.getuser() @@ -701,7 +701,7 @@ class Actor(object): return actor @classmethod - def committer(cls, config_reader: Union[None, GitConfigParser, SectionConstraint] = 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 @@ -748,7 +748,7 @@ class Stats(object): from git.types import Total_TD, Files_TD - def __init__(self, total: Total_TD, files: Dict[str, Files_TD]): + def __init__(self, total: Total_TD, files: Dict[PathLike, Files_TD]): self.total = total self.files = files -- cgit v1.2.1 From 59c89441fb81b0f4549e4bf7ab01f4c27da54aad Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 23:37:41 +0100 Subject: forward ref Gitconfigparser --- git/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git/util.py') diff --git a/git/util.py b/git/util.py index bcc634ec..eccaa74e 100644 --- a/git/util.py +++ b/git/util.py @@ -712,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: Union[None, GitConfigParser, SectionConstraint] = 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) -- cgit v1.2.1