From 42e4f5e26b812385df65f8f32081035e2fb2a121 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 05:52:48 +0100 Subject: Add types to tree.Tree --- git/index/base.py | 2 +- git/index/fun.py | 2 +- git/objects/fun.py | 15 +++++++++++--- git/objects/tree.py | 56 ++++++++++++++++++++++++++--------------------------- git/repo/base.py | 3 ++- 5 files changed, 44 insertions(+), 34 deletions(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index 04424060..e2b3f8fa 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -568,7 +568,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # note: additional deserialization could be saved if write_tree_from_cache # would return sorted tree entries root_tree = Tree(self.repo, binsha, path='') - root_tree._cache = tree_items + root_tree._cache = tree_items # type: ignore return root_tree def _process_diff_args(self, args: List[Union[str, diff.Diffable, object]] diff --git a/git/index/fun.py b/git/index/fun.py index 3fded347..10a44050 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -293,7 +293,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 # finally create the tree sio = BytesIO() tree_to_stream(tree_items, sio.write) # converts bytes of each item[0] to str - tree_items_stringified = cast(List[Tuple[str, int, str]], tree_items) # type: List[Tuple[str, int, str]] + tree_items_stringified = cast(List[Tuple[str, int, str]], tree_items) sio.seek(0) istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio)) diff --git a/git/objects/fun.py b/git/objects/fun.py index 9b36712e..339a53b8 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -1,10 +1,19 @@ """Module with functions which are supposed to be as fast as possible""" from stat import S_ISDIR + from git.compat import ( safe_decode, defenc ) +# typing ---------------------------------------------- + +from typing import List, Tuple + + +# --------------------------------------------------- + + __all__ = ('tree_to_stream', 'tree_entries_from_data', 'traverse_trees_recursive', 'traverse_tree_recursive') @@ -38,7 +47,7 @@ def tree_to_stream(entries, write): # END for each item -def tree_entries_from_data(data): +def tree_entries_from_data(data: bytes) -> List[Tuple[bytes, int, str]]: """Reads the binary representation of a tree and returns tuples of Tree items :param data: data block with tree data (as bytes) :return: list(tuple(binsha, mode, tree_relative_path), ...)""" @@ -72,8 +81,8 @@ def tree_entries_from_data(data): # default encoding for strings in git is utf8 # Only use the respective unicode object if the byte stream was encoded - name = data[ns:i] - name = safe_decode(name) + name_bytes = data[ns:i] + name = safe_decode(name_bytes) # byte is NULL, get next 20 i += 1 diff --git a/git/objects/tree.py b/git/objects/tree.py index 29b2a684..ec7d8e88 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -20,20 +20,23 @@ from .fun import ( # typing ------------------------------------------------- -from typing import Iterable, Iterator, Tuple, Union, cast, TYPE_CHECKING +from typing import Callable, Dict, Iterable, Iterator, List, Tuple, Type, Union, cast, TYPE_CHECKING + +from git.types import PathLike if TYPE_CHECKING: + from git.repo import Repo from io import BytesIO #-------------------------------------------------------- -cmp = lambda a, b: (a > b) - (a < b) +cmp: Callable[[int, int], int] = lambda a, b: (a > b) - (a < b) __all__ = ("TreeModifier", "Tree") -def git_cmp(t1, t2): +def git_cmp(t1: 'Tree', t2: 'Tree') -> int: a, b = t1[2], t2[2] len_a, len_b = len(a), len(b) min_len = min(len_a, len_b) @@ -45,9 +48,9 @@ def git_cmp(t1, t2): return len_a - len_b -def merge_sort(a, cmp): +def merge_sort(a: List[int], cmp: Callable[[int, int], int]) -> None: if len(a) < 2: - return + return None mid = len(a) // 2 lefthalf = a[:mid] @@ -182,29 +185,29 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): symlink_id = 0o12 tree_id = 0o04 - _map_id_to_type = { + _map_id_to_type: Dict[int, Union[Type[Submodule], Type[Blob], Type['Tree']]] = { commit_id: Submodule, blob_id: Blob, symlink_id: Blob # tree id added once Tree is defined } - def __init__(self, repo, binsha, mode=tree_id << 12, path=None): + def __init__(self, repo: 'Repo', binsha: bytes, mode: int = tree_id << 12, path: Union[PathLike, None] = None): super(Tree, self).__init__(repo, binsha, mode, path) @classmethod def _get_intermediate_items(cls, index_object: 'Tree', # type: ignore - ) -> Tuple['Tree', ...]: + ) -> Union[Tuple['Tree', ...], Tuple[()]]: if index_object.type == "tree": index_object = cast('Tree', index_object) return tuple(index_object._iter_convert_to_object(index_object._cache)) return () - def _set_cache_(self, attr): + def _set_cache_(self, attr: str) -> None: if attr == "_cache": # Set the data when we need it ostream = self.repo.odb.stream(self.binsha) - self._cache = tree_entries_from_data(ostream.read()) + self._cache: List[Tuple[bytes, int, str]] = tree_entries_from_data(ostream.read()) else: super(Tree, self)._set_cache_(attr) # END handle attribute @@ -221,7 +224,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path)) from e # END for each item - def join(self, file): + def join(self, file: str) -> Union[Blob, 'Tree', Submodule]: """Find the named object in this tree's contents :return: ``git.Blob`` or ``git.Tree`` or ``git.Submodule`` @@ -254,26 +257,22 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): raise KeyError(msg % file) # END handle long paths - def __div__(self, file): - """For PY2 only""" - return self.join(file) - - def __truediv__(self, file): + def __truediv__(self, file: str) -> Union['Tree', Blob, Submodule]: """For PY3 only""" return self.join(file) @property - def trees(self): + def trees(self) -> List['Tree']: """:return: list(Tree, ...) list of trees directly below this tree""" return [i for i in self if i.type == "tree"] @property - def blobs(self): + def blobs(self) -> List['Blob']: """:return: list(Blob, ...) list of blobs directly below this tree""" return [i for i in self if i.type == "blob"] @property - def cache(self): + def cache(self) -> TreeModifier: """ :return: An object allowing to modify the internal cache. This can be used to change the tree's contents. When done, make sure you call ``set_done`` @@ -289,16 +288,16 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): return super(Tree, self).traverse(predicate, prune, depth, branch_first, visit_once, ignore_self) # List protocol - def __getslice__(self, i, j): + def __getslice__(self, i: int, j: int) -> List[Union[Blob, 'Tree', Submodule]]: return list(self._iter_convert_to_object(self._cache[i:j])) - def __iter__(self): + def __iter__(self) -> Iterator[Union[Blob, 'Tree', Submodule]]: return self._iter_convert_to_object(self._cache) - def __len__(self): + def __len__(self) -> int: return len(self._cache) - def __getitem__(self, item): + def __getitem__(self, item: Union[str, int, slice]) -> Union[Blob, 'Tree', Submodule]: if isinstance(item, int): info = self._cache[item] return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2])) @@ -310,7 +309,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): raise TypeError("Invalid index type: %r" % item) - def __contains__(self, item): + def __contains__(self, item: Union[IndexObject, PathLike]) -> bool: if isinstance(item, IndexObject): for info in self._cache: if item.binsha == info[0]: @@ -321,10 +320,11 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): # compatibility # treat item as repo-relative path - path = self.path - for info in self._cache: - if item == join_path(path, info[2]): - return True + else: + path = self.path + for info in self._cache: + if item == join_path(path, info[2]): + return True # END for each item return False diff --git a/git/repo/base.py b/git/repo/base.py index 5abd4961..77947731 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -7,6 +7,7 @@ import logging import os import re import warnings +from gitdb.db.loose import LooseObjectDB from gitdb.exc import BadObject @@ -100,7 +101,7 @@ class Repo(object): # Subclasses may easily bring in their own custom types by placing a constructor or type here GitCommandWrapperType = Git - def __init__(self, path: Optional[PathLike] = None, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB, + def __init__(self, path: Optional[PathLike] = None, odbt: Type[LooseObjectDB] = GitCmdObjectDB, search_parent_directories: bool = False, expand_vars: bool = True) -> None: """Create a new Repo instance -- 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/objects/commit.py | 4 ++-- git/objects/submodule/base.py | 20 ++++++++++++++------ git/objects/util.py | 11 ++++++----- git/refs/reference.py | 4 ++-- git/remote.py | 31 ++++++++++++++++--------------- git/repo/base.py | 12 ++++++------ git/util.py | 22 ++++++++++++++++------ 7 files changed, 62 insertions(+), 42 deletions(-) (limited to 'git') diff --git a/git/objects/commit.py b/git/objects/commit.py index 26db6e36..0b707450 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -8,7 +8,7 @@ from gitdb import IStream from git.util import ( hex_to_bin, Actor, - Iterable, + IterableObj, Stats, finalize_process ) @@ -47,7 +47,7 @@ log.addHandler(logging.NullHandler()) __all__ = ('Commit', ) -class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): +class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): """Wraps a git Commit object. diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 8cf4dd1e..57396a46 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -3,7 +3,6 @@ from io import BytesIO import logging import os import stat -from typing import List from unittest import SkipTest import uuid @@ -27,7 +26,7 @@ from git.exc import ( from git.objects.base import IndexObject, Object from git.objects.util import Traversable from git.util import ( - Iterable, + IterableObj, join_path_native, to_native_path_linux, RemoteProgress, @@ -47,6 +46,15 @@ from .util import ( ) +# typing ---------------------------------------------------------------------- + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from git.util import IterableList + +# ----------------------------------------------------------------------------- + __all__ = ["Submodule", "UpdateProgress"] @@ -74,7 +82,7 @@ UPDWKTREE = UpdateProgress.UPDWKTREE # IndexObject comes via util module, its a 'hacky' fix thanks to pythons import # mechanism which cause plenty of trouble of the only reason for packages and # modules is refactoring - subpackages shouldn't depend on parent packages -class Submodule(IndexObject, Iterable, Traversable): +class Submodule(IndexObject, IterableObj, Traversable): """Implements access to a git submodule. They are special in that their sha represents a commit in the submodule's repository which is to be checked out @@ -136,12 +144,12 @@ class Submodule(IndexObject, Iterable, Traversable): # END handle attribute name @classmethod - def _get_intermediate_items(cls, item: 'Submodule') -> List['Submodule']: # type: ignore + def _get_intermediate_items(cls, item: 'Submodule') -> IterableList['Submodule']: """:return: all the submodules of our module repository""" try: return cls.list_items(item.module()) except InvalidGitRepositoryError: - return [] + return IterableList('') # END handle intermediate items @classmethod @@ -1163,7 +1171,7 @@ class Submodule(IndexObject, Iterable, Traversable): :raise IOError: If the .gitmodules file/blob could not be read""" return self._config_parser_constrained(read_only=True) - def children(self): + def children(self) -> IterableList['Submodule']: """ :return: IterableList(Submodule, ...) an iterable list of submodules instances which are children of this submodule or 0 if the submodule is not checked out""" diff --git a/git/objects/util.py b/git/objects/util.py index 087f0166..a565cf42 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -19,11 +19,11 @@ import calendar from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import (Any, Callable, Deque, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload) +from typing import (Any, Callable, Deque, Iterator, TYPE_CHECKING, Tuple, Type, Union, cast) if TYPE_CHECKING: from io import BytesIO, StringIO - from .submodule.base import Submodule + from .submodule.base import Submodule # noqa: F401 from .commit import Commit from .blob import Blob from .tag import TagObject @@ -284,6 +284,7 @@ class Traversable(object): """ __slots__ = () + """ @overload @classmethod def _get_intermediate_items(cls, item: 'Commit') -> Tuple['Commit', ...]: @@ -303,10 +304,10 @@ class Traversable(object): @classmethod def _get_intermediate_items(cls, item: 'Traversable') -> Tuple['Traversable', ...]: ... + """ @classmethod - def _get_intermediate_items(cls, item: 'Traversable' - ) -> Sequence['Traversable']: + def _get_intermediate_items(cls, item): """ Returns: Tuple of items connected to the given item. @@ -322,7 +323,7 @@ class Traversable(object): """ :return: IterableList with the results of the traversal as produced by traverse()""" - out = IterableList(self._id_attribute_) # type: ignore[attr-defined] # defined in sublcasses + out: IterableList = IterableList(self._id_attribute_) # type: ignore[attr-defined] # defined in sublcasses out.extend(self.traverse(*args, **kwargs)) return out diff --git a/git/refs/reference.py b/git/refs/reference.py index 9014f555..8a9b0487 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -1,6 +1,6 @@ from git.util import ( LazyMixin, - Iterable, + IterableObj, ) from .symbolic import SymbolicReference @@ -23,7 +23,7 @@ def require_remote_ref_path(func): #}END utilities -class Reference(SymbolicReference, LazyMixin, Iterable): +class Reference(SymbolicReference, LazyMixin, IterableObj): """Represents a named reference to any object. Subclasses may apply restrictions though, i.e. Heads can only point to commits.""" diff --git a/git/remote.py b/git/remote.py index 6ea4b2a1..a85297c1 100644 --- a/git/remote.py +++ b/git/remote.py @@ -13,7 +13,7 @@ from git.compat import (defenc, force_text) from git.exc import GitCommandError from git.util import ( LazyMixin, - Iterable, + IterableObj, IterableList, RemoteProgress, CallableRemoteProgress, @@ -107,7 +107,7 @@ def to_progress_instance(progress: Union[Callable[..., Any], RemoteProgress, Non return progress -class PushInfo(object): +class PushInfo(IterableObj, object): """ Carries information about the result of a push operation of a single head:: @@ -220,7 +220,7 @@ class PushInfo(object): return PushInfo(flags, from_ref, to_ref_string, remote, old_commit, summary) -class FetchInfo(object): +class FetchInfo(IterableObj, object): """ Carries information about the results of a fetch operation of a single head:: @@ -421,7 +421,7 @@ class FetchInfo(object): return cls(remote_local_ref, flags, note, old_commit, local_remote_ref) -class Remote(LazyMixin, Iterable): +class Remote(LazyMixin, IterableObj): """Provides easy read and write access to a git remote. @@ -580,18 +580,18 @@ class Remote(LazyMixin, Iterable): raise ex @property - def refs(self) -> IterableList: + def refs(self) -> IterableList[RemoteReference]: """ :return: IterableList of RemoteReference objects. It is prefixed, allowing you to omit the remote path portion, i.e.:: remote.refs.master # yields RemoteReference('/refs/remotes/origin/master')""" - out_refs = IterableList(RemoteReference._id_attribute_, "%s/" % self.name) + out_refs: IterableList[RemoteReference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name) out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name)) return out_refs @property - def stale_refs(self) -> IterableList: + def stale_refs(self) -> IterableList[Reference]: """ :return: IterableList RemoteReference objects that do not have a corresponding @@ -606,7 +606,7 @@ class Remote(LazyMixin, Iterable): as well. This is a fix for the issue described here: https://github.com/gitpython-developers/GitPython/issues/260 """ - out_refs = IterableList(RemoteReference._id_attribute_, "%s/" % self.name) + out_refs: IterableList[RemoteReference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name) for line in self.repo.git.remote("prune", "--dry-run", self).splitlines()[2:]: # expecting # * [would prune] origin/new_branch @@ -681,11 +681,12 @@ class Remote(LazyMixin, Iterable): return self def _get_fetch_info_from_stderr(self, proc: TBD, - progress: Union[Callable[..., Any], RemoteProgress, None]) -> IterableList: + progress: Union[Callable[..., Any], RemoteProgress, None] + ) -> IterableList['FetchInfo']: progress = to_progress_instance(progress) # skip first line as it is some remote info we are not interested in - output = IterableList('name') + output: IterableList['FetchInfo'] = IterableList('name') # lines which are no progress are fetch info lines # this also waits for the command to finish @@ -741,7 +742,7 @@ class Remote(LazyMixin, Iterable): return output def _get_push_info(self, proc: TBD, - progress: Union[Callable[..., Any], RemoteProgress, None]) -> IterableList: + progress: Union[Callable[..., Any], RemoteProgress, None]) -> IterableList[PushInfo]: progress = to_progress_instance(progress) # read progress information from stderr @@ -749,7 +750,7 @@ class Remote(LazyMixin, Iterable): # read the lines manually as it will use carriage returns between the messages # to override the previous one. This is why we read the bytes manually progress_handler = progress.new_message_handler() - output = IterableList('push_infos') + output: IterableList[PushInfo] = IterableList('push_infos') def stdout_handler(line: str) -> None: try: @@ -785,7 +786,7 @@ class Remote(LazyMixin, Iterable): def fetch(self, refspec: Union[str, List[str], None] = None, progress: Union[Callable[..., Any], None] = None, - verbose: bool = True, **kwargs: Any) -> IterableList: + verbose: bool = True, **kwargs: Any) -> IterableList[FetchInfo]: """Fetch the latest changes for this remote :param refspec: @@ -832,7 +833,7 @@ class Remote(LazyMixin, Iterable): def pull(self, refspec: Union[str, List[str], None] = None, progress: Union[Callable[..., Any], None] = None, - **kwargs: Any) -> IterableList: + **kwargs: Any) -> IterableList[FetchInfo]: """Pull changes from the given branch, being the same as a fetch followed by a merge of branch with your local branch. @@ -853,7 +854,7 @@ class Remote(LazyMixin, Iterable): def push(self, refspec: Union[str, List[str], None] = None, progress: Union[Callable[..., Any], None] = None, - **kwargs: Any) -> IterableList: + **kwargs: Any) -> IterableList[PushInfo]: """Push changes from source branch in refspec to target branch in refspec. :param refspec: see 'fetch' method diff --git a/git/repo/base.py b/git/repo/base.py index 77947731..52727504 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -309,7 +309,7 @@ class Repo(object): return self._bare @property - def heads(self) -> 'IterableList': + def heads(self) -> 'IterableList[Head]': """A list of ``Head`` objects representing the branch heads in this repo @@ -317,7 +317,7 @@ class Repo(object): return Head.list_items(self) @property - def references(self) -> 'IterableList': + def references(self) -> 'IterableList[Reference]': """A list of Reference objects representing tags, heads and remote references. :return: IterableList(Reference, ...)""" @@ -342,7 +342,7 @@ class Repo(object): return HEAD(self, 'HEAD') @property - def remotes(self) -> 'IterableList': + def remotes(self) -> 'IterableList[Remote]': """A list of Remote objects allowing to access and manipulate remotes :return: ``git.IterableList(Remote, ...)``""" return Remote.list_items(self) @@ -358,13 +358,13 @@ class Repo(object): #{ Submodules @property - def submodules(self) -> 'IterableList': + def submodules(self) -> 'IterableList[Submodule]': """ :return: git.IterableList(Submodule, ...) of direct submodules available from the current head""" return Submodule.list_items(self) - def submodule(self, name: str) -> 'IterableList': + def submodule(self, name: str) -> 'Submodule': """ :return: Submodule with the given name :raise ValueError: If no such submodule exists""" try: @@ -396,7 +396,7 @@ class Repo(object): #}END submodules @property - def tags(self) -> 'IterableList': + def tags(self) -> 'IterableList[TagReference]': """A list of ``Tag`` objects that are available in this repo :return: ``git.IterableList(TagReference, ...)`` """ return TagReference.list_items(self) 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') 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') 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 8bf00a6719804c2fc5cca280e9dae6774acc1237 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 16:16:32 +0100 Subject: fix an import --- git/objects/submodule/base.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 57396a46..ce0f944e 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -31,7 +31,8 @@ from git.util import ( to_native_path_linux, RemoteProgress, rmtree, - unbare_repo + unbare_repo, + IterableList ) from git.util import HIDE_WINDOWS_KNOWN_ERRORS @@ -48,10 +49,6 @@ from .util import ( # typing ---------------------------------------------------------------------- -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from git.util import IterableList # ----------------------------------------------------------------------------- -- cgit v1.2.1 From 26dfeb66be61e9a2a9087bdecc98d255c0306079 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 16:23:15 +0100 Subject: fix indent --- git/objects/util.py | 22 ---------------------- 1 file changed, 22 deletions(-) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index a565cf42..71137264 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -284,28 +284,6 @@ class Traversable(object): """ __slots__ = () - """ - @overload - @classmethod - def _get_intermediate_items(cls, item: 'Commit') -> Tuple['Commit', ...]: - ... - - @overload - @classmethod - def _get_intermediate_items(cls, item: 'Submodule') -> Tuple['Submodule', ...]: - ... - - @overload - @classmethod - def _get_intermediate_items(cls, item: 'Tree') -> Tuple['Tree', ...]: - ... - - @overload - @classmethod - def _get_intermediate_items(cls, item: 'Traversable') -> Tuple['Traversable', ...]: - ... - """ - @classmethod def _get_intermediate_items(cls, item): """ -- 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') 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/objects/submodule/base.py | 2 +- git/util.py | 35 ++++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index ce0f944e..cbf6cd0d 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -1158,7 +1158,7 @@ class Submodule(IndexObject, IterableObj, Traversable): """ return self._name - def config_reader(self): + def config_reader(self) -> SectionConstraint: """ :return: ConfigReader instance which allows you to qurey the configuration values of this submodule, as provided by the .gitmodules file 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/types.py | 24 +++++++++++++++++++++--- git/util.py | 25 +++++++++++++------------ 2 files changed, 34 insertions(+), 15 deletions(-) (limited to 'git') diff --git a/git/types.py b/git/types.py index a410cb36..8c431e53 100644 --- a/git/types.py +++ b/git/types.py @@ -4,12 +4,12 @@ import os import sys -from typing import Union, Any +from typing import Dict, Union, Any if sys.version_info[:2] >= (3, 8): - from typing import Final, Literal, SupportsIndex # noqa: F401 + from typing import Final, Literal, SupportsIndex, TypedDict # noqa: F401 else: - from typing_extensions import Final, Literal, SupportsIndex # noqa: F401 + from typing_extensions import Final, Literal, SupportsIndex, TypedDict # noqa: F401 if sys.version_info[:2] < (3, 9): @@ -22,3 +22,21 @@ elif sys.version_info[:2] >= (3, 9): TBD = Any Lit_config_levels = Literal['system', 'global', 'user', 'repository'] + + +class Files_TD(TypedDict): + insertions: int + deletions: int + lines: int + + +class Total_TD(TypedDict): + insertions: int + deletions: int + lines: int + files: int + + +class HSH_TD(TypedDict): + total: Total_TD + files: Dict[str, Files_TD] 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/objects/tree.py | 32 ++++++++++++++++++-------------- git/types.py | 2 +- git/util.py | 6 +++--- 3 files changed, 22 insertions(+), 18 deletions(-) (limited to 'git') diff --git a/git/objects/tree.py b/git/objects/tree.py index ec7d8e88..97a4b748 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -20,7 +20,7 @@ from .fun import ( # typing ------------------------------------------------- -from typing import Callable, Dict, Iterable, Iterator, List, Tuple, Type, Union, cast, TYPE_CHECKING +from typing import Callable, Dict, Generic, Iterable, Iterator, List, Tuple, Type, TypeVar, Union, cast, TYPE_CHECKING from git.types import PathLike @@ -31,13 +31,16 @@ if TYPE_CHECKING: #-------------------------------------------------------- -cmp: Callable[[int, int], int] = lambda a, b: (a > b) - (a < b) +cmp: Callable[[str, str], int] = lambda a, b: (a > b) - (a < b) __all__ = ("TreeModifier", "Tree") +T_Tree_cache = TypeVar('T_Tree_cache', bound=Union[Tuple[bytes, int, str]]) -def git_cmp(t1: 'Tree', t2: 'Tree') -> int: + +def git_cmp(t1: T_Tree_cache, t2: T_Tree_cache) -> int: a, b = t1[2], t2[2] + assert isinstance(a, str) and isinstance(b, str) # Need as mypy 9.0 cannot unpack TypeVar properly len_a, len_b = len(a), len(b) min_len = min(len_a, len_b) min_cmp = cmp(a[:min_len], b[:min_len]) @@ -48,7 +51,8 @@ def git_cmp(t1: 'Tree', t2: 'Tree') -> int: return len_a - len_b -def merge_sort(a: List[int], cmp: Callable[[int, int], int]) -> None: +def merge_sort(a: List[T_Tree_cache], + cmp: Callable[[T_Tree_cache, T_Tree_cache], int]) -> None: if len(a) < 2: return None @@ -83,7 +87,7 @@ def merge_sort(a: List[int], cmp: Callable[[int, int], int]) -> None: k = k + 1 -class TreeModifier(object): +class TreeModifier(Generic[T_Tree_cache], object): """A utility class providing methods to alter the underlying cache in a list-like fashion. @@ -91,10 +95,10 @@ class TreeModifier(object): the cache of a tree, will be sorted. Assuring it will be in a serializable state""" __slots__ = '_cache' - def __init__(self, cache): + def __init__(self, cache: List[T_Tree_cache]) -> None: self._cache = cache - def _index_by_name(self, name): + def _index_by_name(self, name: str) -> int: """:return: index of an item with name, or -1 if not found""" for i, t in enumerate(self._cache): if t[2] == name: @@ -104,7 +108,7 @@ class TreeModifier(object): return -1 #{ Interface - def set_done(self): + def set_done(self) -> 'TreeModifier': """Call this method once you are done modifying the tree information. It may be called several times, but be aware that each call will cause a sort operation @@ -114,7 +118,7 @@ class TreeModifier(object): #} END interface #{ Mutators - def add(self, sha, mode, name, force=False): + def add(self, sha: bytes, mode: int, name: str, force: bool = False) -> 'TreeModifier': """Add the given item to the tree. If an item with the given name already exists, nothing will be done, but a ValueError will be raised if the sha and mode of the existing item do not match the one you add, unless @@ -132,7 +136,7 @@ class TreeModifier(object): sha = to_bin_sha(sha) index = self._index_by_name(name) - item = (sha, mode, name) + item: T_Tree_cache = (sha, mode, name) # type: ignore ## use Typeguard from typing-extensions 3.10.0 if index == -1: self._cache.append(item) else: @@ -195,7 +199,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): def __init__(self, repo: 'Repo', binsha: bytes, mode: int = tree_id << 12, path: Union[PathLike, None] = None): super(Tree, self).__init__(repo, binsha, mode, path) - @classmethod + @ classmethod def _get_intermediate_items(cls, index_object: 'Tree', # type: ignore ) -> Union[Tuple['Tree', ...], Tuple[()]]: if index_object.type == "tree": @@ -261,17 +265,17 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): """For PY3 only""" return self.join(file) - @property + @ property def trees(self) -> List['Tree']: """:return: list(Tree, ...) list of trees directly below this tree""" return [i for i in self if i.type == "tree"] - @property + @ property def blobs(self) -> List['Blob']: """:return: list(Blob, ...) list of blobs directly below this tree""" return [i for i in self if i.type == "blob"] - @property + @ property def cache(self) -> TreeModifier: """ :return: An object allowing to modify the internal cache. This can be used diff --git a/git/types.py b/git/types.py index 8c431e53..c01ea27e 100644 --- a/git/types.py +++ b/git/types.py @@ -39,4 +39,4 @@ class Total_TD(TypedDict): class HSH_TD(TypedDict): total: Total_TD - files: Dict[str, Files_TD] + files: Dict[PathLike, Files_TD] 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') 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 From b72118e231c7bc42f457e2b02e0f90e8f87a5794 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 24 Jun 2021 23:45:14 +0100 Subject: Import TypeGuard to replace casts --- git/types.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'git') diff --git a/git/types.py b/git/types.py index c01ea27e..e3b49170 100644 --- a/git/types.py +++ b/git/types.py @@ -11,6 +11,11 @@ if sys.version_info[:2] >= (3, 8): else: from typing_extensions import Final, Literal, SupportsIndex, TypedDict # noqa: F401 +if sys.version_info[:2] >= (3, 10): + from typing import TypeGuard # noqa: F401 +else: + from typing_extensions import TypeGuard # noqa: F401 + if sys.version_info[:2] < (3, 9): # Python >= 3.6, < 3.9 -- cgit v1.2.1 From a2d9011c05b0e27f1324f393e65954542544250d Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 25 Jun 2021 00:06:15 +0100 Subject: Add asserts and casts for T_Tree_cache --- git/objects/tree.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'git') diff --git a/git/objects/tree.py b/git/objects/tree.py index 97a4b748..191fe27c 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -136,7 +136,9 @@ class TreeModifier(Generic[T_Tree_cache], object): sha = to_bin_sha(sha) index = self._index_by_name(name) - item: T_Tree_cache = (sha, mode, name) # type: ignore ## use Typeguard from typing-extensions 3.10.0 + + assert isinstance(sha, bytes) and isinstance(mode, int) and isinstance(name, str) + item = cast(T_Tree_cache, (sha, mode, name)) # use Typeguard from typing-extensions 3.10.0 if index == -1: self._cache.append(item) else: @@ -151,14 +153,17 @@ class TreeModifier(Generic[T_Tree_cache], object): # END handle name exists return self - def add_unchecked(self, binsha, mode, name): + def add_unchecked(self, binsha: bytes, mode: int, name: str) -> None: """Add the given item to the tree, its correctness is assumed, which puts the caller into responsibility to assure the input is correct. For more information on the parameters, see ``add`` :param binsha: 20 byte binary sha""" - self._cache.append((binsha, mode, name)) + assert isinstance(binsha, bytes) and isinstance(mode, int) and isinstance(name, str) + tree_cache = cast(T_Tree_cache, (binsha, mode, name)) + + self._cache.append(tree_cache) - def __delitem__(self, name): + def __delitem__(self, name: str) -> None: """Deletes an item with the given name if it exists""" index = self._index_by_name(name) if index > -1: -- cgit v1.2.1 From 0eae33d324376a0a1800e51bddf7f23a343f45a1 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 25 Jun 2021 20:21:59 +0100 Subject: Add is_flatLiteral() Typeguard[] to remote.py --- git/remote.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/remote.py b/git/remote.py index a85297c1..2bf64150 100644 --- a/git/remote.py +++ b/git/remote.py @@ -38,7 +38,7 @@ from .refs import ( from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union, cast, overload -from git.types import PathLike, Literal, TBD +from git.types import PathLike, Literal, TBD, TypeGuard if TYPE_CHECKING: from git.repo.base import Repo @@ -48,8 +48,15 @@ if TYPE_CHECKING: from git.objects.tag import TagObject flagKeyLiteral = Literal[' ', '!', '+', '-', '*', '=', 't'] + + +def is_flagKeyLiteral(inp: str) -> TypeGuard[flagKeyLiteral]: + return inp in [' ', '!', '+', '-', '=', '*', 't'] + + # ------------------------------------------------------------- + log = logging.getLogger('git.remote') log.addHandler(logging.NullHandler()) @@ -325,7 +332,7 @@ class FetchInfo(IterableObj, object): # parse lines control_character, operation, local_remote_ref, remote_local_ref_str, note = match.groups() - control_character = cast(flagKeyLiteral, control_character) # can do this neater once 3.5 dropped + assert is_flagKeyLiteral(control_character) try: _new_hex_sha, _fetch_operation, fetch_note = fetch_line.split("\t") -- cgit v1.2.1 From 5b0465c9bcca64c3a863a95735cc5e602946facb Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 25 Jun 2021 20:27:22 +0100 Subject: fix assert --- git/remote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/remote.py b/git/remote.py index 2bf64150..748dcbbd 100644 --- a/git/remote.py +++ b/git/remote.py @@ -332,7 +332,7 @@ class FetchInfo(IterableObj, object): # parse lines control_character, operation, local_remote_ref, remote_local_ref_str, note = match.groups() - assert is_flagKeyLiteral(control_character) + assert is_flagKeyLiteral(control_character), f"{control_character}" try: _new_hex_sha, _fetch_operation, fetch_note = fetch_line.split("\t") -- cgit v1.2.1 From dc8d23d3d6e735d70fd0a60641c58f6e44e17029 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 25 Jun 2021 20:30:44 +0100 Subject: Add '?' to controlcharacter literal --- git/remote.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/remote.py b/git/remote.py index 748dcbbd..e6daffe0 100644 --- a/git/remote.py +++ b/git/remote.py @@ -47,11 +47,11 @@ if TYPE_CHECKING: from git.objects.tree import Tree from git.objects.tag import TagObject -flagKeyLiteral = Literal[' ', '!', '+', '-', '*', '=', 't'] +flagKeyLiteral = Literal[' ', '!', '+', '-', '*', '=', 't', '?'] def is_flagKeyLiteral(inp: str) -> TypeGuard[flagKeyLiteral]: - return inp in [' ', '!', '+', '-', '=', '*', 't'] + return inp in [' ', '!', '+', '-', '=', '*', 't', '?'] # ------------------------------------------------------------- -- cgit v1.2.1 From 7b09003fffa8196277bcfaa9984a3e6833805a6d Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 25 Jun 2021 20:52:29 +0100 Subject: replace cast()s with asserts in remote.py --- git/refs/log.py | 12 ++++++------ git/remote.py | 12 +++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) (limited to 'git') diff --git a/git/refs/log.py b/git/refs/log.py index 363c3c5d..f850ba24 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -82,23 +82,23 @@ class RefLogEntry(tuple): return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), message)) @classmethod - def from_line(cls, line): + def from_line(cls, line: bytes) -> 'RefLogEntry': """:return: New RefLogEntry instance from the given revlog line. :param line: line bytes without trailing newline :raise ValueError: If line could not be parsed""" - line = line.decode(defenc) - fields = line.split('\t', 1) + line_str = line.decode(defenc) + fields = line_str.split('\t', 1) if len(fields) == 1: info, msg = fields[0], None elif len(fields) == 2: info, msg = fields else: raise ValueError("Line must have up to two TAB-separated fields." - " Got %s" % repr(line)) + " Got %s" % repr(line_str)) # END handle first split - oldhexsha = info[:40] # type: str - newhexsha = info[41:81] # type: str + oldhexsha = info[:40] + newhexsha = info[41:81] for hexsha in (oldhexsha, newhexsha): if not cls._re_hexsha_only.match(hexsha): raise ValueError("Invalid hexsha: %r" % (hexsha,)) diff --git a/git/remote.py b/git/remote.py index e6daffe0..a6232db3 100644 --- a/git/remote.py +++ b/git/remote.py @@ -36,7 +36,7 @@ from .refs import ( # typing------------------------------------------------------- -from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union, cast, overload +from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union, overload from git.types import PathLike, Literal, TBD, TypeGuard @@ -559,8 +559,8 @@ class Remote(LazyMixin, IterableObj): def urls(self) -> Iterator[str]: """:return: Iterator yielding all configured URL targets on a remote as strings""" try: - # can replace cast with type assert? - remote_details = cast(str, self.repo.git.remote("get-url", "--all", self.name)) + remote_details = self.repo.git.remote("get-url", "--all", self.name) + assert isinstance(remote_details, str) for line in remote_details.split('\n'): yield line except GitCommandError as ex: @@ -571,14 +571,16 @@ class Remote(LazyMixin, IterableObj): # if 'Unknown subcommand: get-url' in str(ex): try: - remote_details = cast(str, self.repo.git.remote("show", self.name)) + remote_details = self.repo.git.remote("show", self.name) + assert isinstance(remote_details, str) for line in remote_details.split('\n'): if ' Push URL:' in line: yield line.split(': ')[-1] except GitCommandError as _ex: if any(msg in str(_ex) for msg in ['correct access rights', 'cannot run ssh']): # If ssh is not setup to access this repository, see issue 694 - remote_details = cast(str, self.repo.git.config('--get-all', 'remote.%s.url' % self.name)) + remote_details = self.repo.git.config('--get-all', 'remote.%s.url' % self.name) + assert isinstance(remote_details, str) for line in remote_details.split('\n'): yield line else: -- cgit v1.2.1 From aba4d9b4029373d2bccc961a23134454072936ce Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 25 Jun 2021 21:03:17 +0100 Subject: replace cast()s with asserts in fun.py --- git/index/fun.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index 10a44050..ffd109b1 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -53,7 +53,7 @@ from .util import ( from typing import (Dict, IO, List, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast) -from git.types import PathLike +from git.types import PathLike, TypeGuard if TYPE_CHECKING: from .base import IndexFile @@ -185,11 +185,17 @@ def read_header(stream: IO[bytes]) -> Tuple[int, int]: def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, int]: """:return: Key suitable to be used for the index.entries dictionary :param entry: One instance of type BaseIndexEntry or the path and the stage""" + + def is_entry_tuple(entry: Tuple) -> TypeGuard[Tuple[PathLike, int]]: + return isinstance(entry, tuple) and len(entry) == 2 + if len(entry) == 1: - entry_first = cast(BaseIndexEntry, entry[0]) # type: BaseIndexEntry + entry_first = entry[0] + assert isinstance(entry_first, BaseIndexEntry) return (entry_first.path, entry_first.stage) else: - entry = cast(Tuple[PathLike, int], tuple(entry)) + # entry = tuple(entry) + assert is_entry_tuple(entry) return entry # END handle entry -- cgit v1.2.1 From 07bfe1a60ae93d8b40c9aa01a3775f334d680daa Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 25 Jun 2021 21:23:03 +0100 Subject: trigger checks to rurun --- git/objects/submodule/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index 0b4ce3c5..045fb47d 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -65,7 +65,7 @@ class SubmoduleConfigParser(GitConfigParser): the first write operation begins""" self._smref = weakref.ref(submodule) - def flush_to_index(self): + def flush_to_index(self) -> None: """Flush changes in our configuration file to the index""" assert self._smref is not None # should always have a file here -- cgit v1.2.1 From 09fb2274db09e44bf3bc14da482ffa9a98659c54 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 25 Jun 2021 21:29:55 +0100 Subject: Add type to submodule to trigger checks to rurun --- git/objects/submodule/util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'git') diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index 045fb47d..b4796b30 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -4,6 +4,11 @@ from git.config import GitConfigParser from io import BytesIO import weakref +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .base import Submodule + __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', 'SubmoduleConfigParser') @@ -60,7 +65,7 @@ class SubmoduleConfigParser(GitConfigParser): super(SubmoduleConfigParser, self).__init__(*args, **kwargs) #{ Interface - def set_submodule(self, submodule): + def set_submodule(self, submodule: 'Submodule') -> None: """Set this instance's submodule. It must be called before the first write operation begins""" self._smref = weakref.ref(submodule) -- cgit v1.2.1 From eff48b8ba25a0ea36a7286aa16d8888315eb1205 Mon Sep 17 00:00:00 2001 From: Dominic Date: Fri, 25 Jun 2021 21:38:42 +0100 Subject: Import typevar in util.py --- git/objects/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index 71137264..7736a0b2 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -19,7 +19,7 @@ import calendar from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import (Any, Callable, Deque, Iterator, TYPE_CHECKING, Tuple, Type, Union, cast) +from typing import (Any, Callable, Deque, Iterator, typevar, TYPE_CHECKING, Tuple, Type, Union, cast) if TYPE_CHECKING: from io import BytesIO, StringIO @@ -29,6 +29,8 @@ if TYPE_CHECKING: from .tag import TagObject from .tree import Tree from subprocess import Popen + +T_Iterableobj = typevar('T_Iterableobj', bound=T_Iterableobj) # -------------------------------------------------------------------- -- cgit v1.2.1 From 17c750a0803ae222f1cdaf3d6282a7e1b2046adb Mon Sep 17 00:00:00 2001 From: Dominic Date: Fri, 25 Jun 2021 21:40:41 +0100 Subject: flake8 fix --- git/objects/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index 7736a0b2..79bf73ae 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: from .tree import Tree from subprocess import Popen -T_Iterableobj = typevar('T_Iterableobj', bound=T_Iterableobj) +T_Iterableobj = typevar('T_Iterableobj') # -------------------------------------------------------------------- -- cgit v1.2.1 From ff56dbbfceef2211087aed2619b7da2e42f235e4 Mon Sep 17 00:00:00 2001 From: Dominic Date: Fri, 25 Jun 2021 21:43:10 +0100 Subject: fix typo --- git/objects/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index 79bf73ae..4609a80b 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -19,7 +19,7 @@ import calendar from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import (Any, Callable, Deque, Iterator, typevar, TYPE_CHECKING, Tuple, Type, Union, cast) +from typing import (Any, Callable, Deque, Iterator, TypeVar, TYPE_CHECKING, Tuple, Type, Union, cast) if TYPE_CHECKING: from io import BytesIO, StringIO -- cgit v1.2.1 From 5d7b8ba9f2e9298496232e4ae66bd904a1d71001 Mon Sep 17 00:00:00 2001 From: Dominic Date: Fri, 25 Jun 2021 21:44:54 +0100 Subject: another typo --- git/objects/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index 4609a80b..8b8148a9 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: from .tree import Tree from subprocess import Popen -T_Iterableobj = typevar('T_Iterableobj') +T_Iterableobj = TypeVar('T_Iterableobj') # -------------------------------------------------------------------- -- cgit v1.2.1