From 29e12e9ceee59a87984c9049ac84e030a4dd0ed2 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:56:38 +0100 Subject: Make traversable and serilizable into protocols --- git/objects/util.py | 78 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 24 deletions(-) (limited to 'git/objects') diff --git a/git/objects/util.py b/git/objects/util.py index fbe3d9de..04af3b83 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -5,6 +5,8 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php """Module for general utility functions""" +from abc import abstractmethod +import warnings from git.util import ( IterableList, IterableObj, @@ -23,7 +25,7 @@ from datetime import datetime, timedelta, tzinfo from typing import (Any, Callable, Deque, Iterator, NamedTuple, overload, Sequence, TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast) -from git.types import Has_id_attribute, Literal +from git.types import Has_id_attribute, Literal, Protocol, runtime_checkable if TYPE_CHECKING: from io import BytesIO, StringIO @@ -289,7 +291,8 @@ class ProcessStreamAdapter(object): return getattr(self._stream, attr) -class Traversable(object): +@runtime_checkable +class Traversable(Protocol): """Simple interface to perform depth-first or breadth-first traversals into one direction. @@ -301,6 +304,7 @@ class Traversable(object): __slots__ = () @classmethod + @abstractmethod def _get_intermediate_items(cls, item) -> Sequence['Traversable']: """ Returns: @@ -313,7 +317,18 @@ class Traversable(object): """ raise NotImplementedError("To be implemented in subclass") - def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']]: + @abstractmethod + def list_traverse(self, *args: Any, **kwargs: Any) -> Any: + """ """ + warnings.warn("list_traverse() method should only be called from subclasses." + "Calling from Traversable abstract class will raise NotImplementedError in 3.1.20" + "Builtin sublclasses are 'Submodule', 'Tree' and 'Commit", + DeprecationWarning, + stacklevel=2) + return self._list_traverse(*args, **kwargs) + + def _list_traverse(self, as_edge=False, *args: Any, **kwargs: Any + ) -> IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']]: """ :return: IterableList with the results of the traversal as produced by traverse() @@ -329,22 +344,34 @@ class Traversable(object): id = "" # shouldn't reach here, unless Traversable subclass created with no _id_attribute_ # could add _id_attribute_ to Traversable, or make all Traversable also Iterable? - out: IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']] = IterableList(id) - # overloads in subclasses (mypy does't allow typing self: subclass) - # Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]] - - # NOTE: if is_edge=True, self.traverse returns a Tuple, so should be prevented or flattened? - kwargs['as_edge'] = False - out.extend(self.traverse(*args, **kwargs)) # type: ignore - return out - - def traverse(self, - predicate: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: True, - prune: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: False, - depth: int = -1, branch_first: bool = True, visit_once: bool = True, - ignore_self: int = 1, as_edge: bool = False - ) -> Union[Iterator[Union['Traversable', 'Blob']], - Iterator[TraversedTup]]: + if not as_edge: + out: IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']] = IterableList(id) + out.extend(self.traverse(as_edge=as_edge, *args, **kwargs)) # type: ignore + return out + # overloads in subclasses (mypy does't allow typing self: subclass) + # Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]] + else: + # Raise deprecationwarning, doesn't make sense to use this + out_list: IterableList = IterableList(self.traverse(*args, **kwargs)) + return out_list + + @ abstractmethod + def traverse(self, *args: Any, **kwargs) -> Any: + """ """ + warnings.warn("traverse() method should only be called from subclasses." + "Calling from Traversable abstract class will raise NotImplementedError in 3.1.20" + "Builtin sublclasses are 'Submodule', 'Tree' and 'Commit", + DeprecationWarning, + stacklevel=2) + return self._traverse(*args, **kwargs) + + def _traverse(self, + predicate: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: True, + prune: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: False, + depth: int = -1, branch_first: bool = True, visit_once: bool = True, + ignore_self: int = 1, as_edge: bool = False + ) -> Union[Iterator[Union['Traversable', 'Blob']], + Iterator[TraversedTup]]: """:return: iterator yielding of items found when traversing self :param predicate: f(i,d) returns False if item i at depth d should not be included in the result @@ -435,11 +462,13 @@ class Traversable(object): # END for each item on work stack -class Serializable(object): +@ runtime_checkable +class Serializable(Protocol): """Defines methods to serialize and deserialize objects from and into a data stream""" __slots__ = () + # @abstractmethod def _serialize(self, stream: 'BytesIO') -> 'Serializable': """Serialize the data of this object into the given data stream :note: a serialized object would ``_deserialize`` into the same object @@ -447,6 +476,7 @@ class Serializable(object): :return: self""" raise NotImplementedError("To be implemented in subclass") + # @abstractmethod def _deserialize(self, stream: 'BytesIO') -> 'Serializable': """Deserialize all information regarding this object from the stream :param stream: a file-like object @@ -454,13 +484,13 @@ class Serializable(object): raise NotImplementedError("To be implemented in subclass") -class TraversableIterableObj(Traversable, IterableObj): +class TraversableIterableObj(IterableObj, Traversable): __slots__ = () TIobj_tuple = Tuple[Union[T_TIobj, None], T_TIobj] - def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TIobj]: # type: ignore[override] - return super(TraversableIterableObj, self).list_traverse(* args, **kwargs) + def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TIobj]: + return super(TraversableIterableObj, self)._list_traverse(* args, **kwargs) @ overload # type: ignore def traverse(self: T_TIobj, @@ -522,6 +552,6 @@ class TraversableIterableObj(Traversable, IterableObj): """ return cast(Union[Iterator[T_TIobj], Iterator[Tuple[Union[None, T_TIobj], T_TIobj]]], - super(TraversableIterableObj, self).traverse( + super(TraversableIterableObj, self)._traverse( predicate, prune, depth, branch_first, visit_once, ignore_self, as_edge # type: ignore )) -- cgit v1.2.1 From 6609ef7c3b5bb840dba8d0a5362e67746761a437 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:57:04 +0100 Subject: Make types in refs compatible with objects --- git/objects/base.py | 6 +++--- git/objects/blob.py | 4 +++- git/objects/commit.py | 21 ++++++++++++--------- git/objects/fun.py | 2 +- git/objects/submodule/base.py | 4 ++-- git/objects/tag.py | 4 +++- git/objects/tree.py | 12 ++++++------ 7 files changed, 30 insertions(+), 23 deletions(-) (limited to 'git/objects') diff --git a/git/objects/base.py b/git/objects/base.py index 4e2ed493..64f105ca 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -15,9 +15,9 @@ from .util import get_object_type_by_name # typing ------------------------------------------------------------------ -from typing import Any, TYPE_CHECKING, Optional, Union +from typing import Any, TYPE_CHECKING, Union -from git.types import PathLike, Commit_ish +from git.types import PathLike, Commit_ish, Lit_commit_ish if TYPE_CHECKING: from git.repo import Repo @@ -44,7 +44,7 @@ class Object(LazyMixin): TYPES = (dbtyp.str_blob_type, dbtyp.str_tree_type, dbtyp.str_commit_type, dbtyp.str_tag_type) __slots__ = ("repo", "binsha", "size") - type = None # type: Optional[str] # to be set by subclass + type: Union[Lit_commit_ish, None] = None def __init__(self, repo: 'Repo', binsha: bytes): """Initialize an object by identifying it by its binary sha. diff --git a/git/objects/blob.py b/git/objects/blob.py index 017178f0..99b5c636 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -6,6 +6,8 @@ from mimetypes import guess_type from . import base +from git.types import Literal + __all__ = ('Blob', ) @@ -13,7 +15,7 @@ class Blob(base.IndexObject): """A Blob encapsulates a git blob object""" DEFAULT_MIME_TYPE = "text/plain" - type = "blob" + type: Literal['blob'] = "blob" # valid blob modes executable_mode = 0o100755 diff --git a/git/objects/commit.py b/git/objects/commit.py index 65a87591..11cf52a5 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -41,10 +41,11 @@ import logging from typing import Any, IO, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING -from git.types import PathLike, TypeGuard +from git.types import PathLike, TypeGuard, Literal if TYPE_CHECKING: from git.repo import Repo + from git.refs import SymbolicReference # ------------------------------------------------------------------------ @@ -73,14 +74,14 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): default_encoding = "UTF-8" # object configuration - type = "commit" + type: Literal['commit'] = "commit" __slots__ = ("tree", "author", "authored_date", "author_tz_offset", "committer", "committed_date", "committer_tz_offset", "message", "parents", "encoding", "gpgsig") _id_attribute_ = "hexsha" - def __init__(self, repo: 'Repo', binsha: bytes, tree: Union['Tree', None] = None, + def __init__(self, repo: 'Repo', binsha: bytes, tree: Union[Tree, None] = None, author: Union[Actor, None] = None, authored_date: Union[int, None] = None, author_tz_offset: Union[None, float] = None, @@ -201,11 +202,11 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): # END handle attrs @property - def authored_datetime(self) -> 'datetime.datetime': + def authored_datetime(self) -> datetime.datetime: return from_timestamp(self.authored_date, self.author_tz_offset) @property - def committed_datetime(self) -> 'datetime.datetime': + def committed_datetime(self) -> datetime.datetime: return from_timestamp(self.committed_date, self.committer_tz_offset) @property @@ -242,7 +243,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): return self.repo.git.name_rev(self) @classmethod - def iter_items(cls, repo: 'Repo', rev: str, # type: ignore + def iter_items(cls, repo: 'Repo', rev: Union[str, 'Commit', 'SymbolicReference'], # type: ignore paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any ) -> Iterator['Commit']: """Find all commits matching the given criteria. @@ -354,7 +355,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): finalize_process(proc_or_stream) @ classmethod - def create_from_tree(cls, repo: 'Repo', tree: Union['Tree', str], message: str, + def create_from_tree(cls, repo: 'Repo', tree: Union[Tree, str], message: str, parent_commits: Union[None, List['Commit']] = None, head: bool = False, author: Union[None, Actor] = None, committer: Union[None, Actor] = None, author_date: Union[None, str] = None, commit_date: Union[None, str] = None): @@ -516,8 +517,10 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): return self def _deserialize(self, stream: BytesIO) -> 'Commit': - """:param from_rev_list: if true, the stream format is coming from the rev-list command - Otherwise it is assumed to be a plain data stream from our object""" + """ + :param from_rev_list: if true, the stream format is coming from the rev-list command + Otherwise it is assumed to be a plain data stream from our object + """ readline = stream.readline self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, '') diff --git a/git/objects/fun.py b/git/objects/fun.py index fc2ea1e7..d6cdafe1 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -167,7 +167,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by data: List[EntryTupOrNone] = [] else: # make new list for typing as list invariant - data = [x for x in tree_entries_from_data(odb.stream(tree_sha).read())] + data = list(tree_entries_from_data(odb.stream(tree_sha).read())) # END handle muted trees trees_data.append(data) # END for each sha to get data for diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index b485dbf6..21cfcd5a 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -52,7 +52,7 @@ from .util import ( from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING, cast from typing import Any, Iterator, Union -from git.types import Commit_ish, PathLike, TBD +from git.types import Commit_ish, Literal, PathLike, TBD if TYPE_CHECKING: from git.index import IndexFile @@ -105,7 +105,7 @@ class Submodule(IndexObject, TraversableIterableObj): k_default_mode = stat.S_IFDIR | stat.S_IFLNK # submodules are directories with link-status # this is a bogus type for base class compatibility - type = 'submodule' + type: Literal['submodule'] = 'submodule' # type: ignore __slots__ = ('_parent_commit', '_url', '_branch_path', '_name', '__weakref__') _cache_attrs = ('path', '_url', '_branch_path') diff --git a/git/objects/tag.py b/git/objects/tag.py index cb6efbe9..4a2fcb0d 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -11,6 +11,8 @@ from ..compat import defenc from typing import List, TYPE_CHECKING, Union +from git.types import Literal + if TYPE_CHECKING: from git.repo import Repo from git.util import Actor @@ -24,7 +26,7 @@ __all__ = ("TagObject", ) class TagObject(base.Object): """Non-Lightweight tag carrying additional information about an object we are pointing to.""" - type = "tag" + type: Literal['tag'] = "tag" __slots__ = ("object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message") def __init__(self, repo: 'Repo', binsha: bytes, diff --git a/git/objects/tree.py b/git/objects/tree.py index a9656c1d..0e3f44b9 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -24,7 +24,7 @@ from .fun import ( from typing import (Any, Callable, Dict, Iterable, Iterator, List, Tuple, Type, Union, cast, TYPE_CHECKING) -from git.types import PathLike, TypeGuard +from git.types import PathLike, TypeGuard, Literal if TYPE_CHECKING: from git.repo import Repo @@ -195,7 +195,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): blob = tree[0] """ - type = "tree" + type: Literal['tree'] = "tree" __slots__ = "_cache" # actual integer ids for comparison @@ -285,7 +285,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): return [i for i in self if i.type == "tree"] @ property - def blobs(self) -> List['Blob']: + 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"] @@ -322,8 +322,8 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): # assert is_tree_traversed(ret_tup), f"Type is {[type(x) for x in list(ret_tup[0])]}" # return ret_tup[0]""" return cast(Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]], - super(Tree, self).traverse(predicate, prune, depth, # type: ignore - branch_first, visit_once, ignore_self)) + super(Tree, self)._traverse(predicate, prune, depth, # type: ignore + branch_first, visit_once, ignore_self)) def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion]: """ @@ -331,7 +331,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): traverse() Tree -> IterableList[Union['Submodule', 'Tree', 'Blob']] """ - return super(Tree, self).list_traverse(* args, **kwargs) + return super(Tree, self)._list_traverse(* args, **kwargs) # List protocol -- cgit v1.2.1 From 9e5e969479ec6018e1ba06b95bcdefca5b0082a4 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 19:10:45 +0100 Subject: Change remaining type comments to py3.6+ types --- git/objects/submodule/base.py | 3 ++- git/objects/tag.py | 4 ++-- git/objects/tree.py | 2 +- git/objects/util.py | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) (limited to 'git/objects') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 21cfcd5a..d5ba118f 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -475,7 +475,8 @@ class Submodule(IndexObject, TraversableIterableObj): sm._branch_path = br.path # we deliberately assume that our head matches our index ! - sm.binsha = mrepo.head.commit.binsha # type: ignore + if mrepo: + sm.binsha = mrepo.head.commit.binsha index.add([sm], write=True) return sm diff --git a/git/objects/tag.py b/git/objects/tag.py index 4a2fcb0d..7048eb40 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -51,7 +51,7 @@ class TagObject(base.Object): authored_date is in, in a format similar to time.altzone""" super(TagObject, self).__init__(repo, binsha) if object is not None: - self.object = object # type: Union['Commit', 'Blob', 'Tree', 'TagObject'] + self.object: Union['Commit', 'Blob', 'Tree', 'TagObject'] = object if tag is not None: self.tag = tag if tagger is not None: @@ -67,7 +67,7 @@ class TagObject(base.Object): """Cache all our attributes at once""" if attr in TagObject.__slots__: ostream = self.repo.odb.stream(self.binsha) - lines = ostream.read().decode(defenc, 'replace').splitlines() # type: List[str] + lines: List[str] = ostream.read().decode(defenc, 'replace').splitlines() _obj, hexsha = lines[0].split(" ") _type_token, type_name = lines[1].split(" ") diff --git a/git/objects/tree.py b/git/objects/tree.py index 0e3f44b9..dd1fe783 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -298,7 +298,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): See the ``TreeModifier`` for more information on how to alter the cache""" return TreeModifier(self._cache) - def traverse(self, # type: ignore # overrides super() + def traverse(self, # type: ignore[override] predicate: Callable[[Union[IndexObjUnion, TraversedTreeTup], int], bool] = lambda i, d: True, prune: Callable[[Union[IndexObjUnion, TraversedTreeTup], int], bool] = lambda i, d: False, depth: int = -1, diff --git a/git/objects/util.py b/git/objects/util.py index 04af3b83..ef1ae77b 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -285,7 +285,7 @@ class ProcessStreamAdapter(object): def __init__(self, process: 'Popen', stream_name: str) -> None: self._proc = process - self._stream = getattr(process, stream_name) # type: StringIO ## guess + self._stream: StringIO = getattr(process, stream_name) # guessed type def __getattr__(self, attr: str) -> Any: return getattr(self._stream, attr) @@ -356,7 +356,7 @@ class Traversable(Protocol): return out_list @ abstractmethod - def traverse(self, *args: Any, **kwargs) -> Any: + def traverse(self, *args: Any, **kwargs: Any) -> Any: """ """ warnings.warn("traverse() method should only be called from subclasses." "Calling from Traversable abstract class will raise NotImplementedError in 3.1.20" @@ -414,7 +414,7 @@ class Traversable(Protocol): ignore_self=False is_edge=False -> Iterator[Tuple[src, item]]""" visited = set() - stack = deque() # type: Deque[TraverseNT] + stack: Deque[TraverseNT] = deque() stack.append(TraverseNT(0, self, None)) # self is always depth level 0 def addToStack(stack: Deque[TraverseNT], -- cgit v1.2.1 From 600df043e76924d43a4f9f88f4e3241740f34c77 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 20:26:53 +0100 Subject: Rmv old py2.7 __future__ imports --- git/objects/__init__.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'git/objects') diff --git a/git/objects/__init__.py b/git/objects/__init__.py index 897eb98f..1d0bb7a5 100644 --- a/git/objects/__init__.py +++ b/git/objects/__init__.py @@ -2,8 +2,6 @@ Import all submodules main classes into the package space """ # flake8: noqa -from __future__ import absolute_import - import inspect from .base import * -- cgit v1.2.1