diff --git a/git/compat/__init__.py b/git/compat.py similarity index 89% rename from git/compat/__init__.py rename to git/compat.py index c4bd2aa36..4ecd19a9a 100644 --- a/git/compat/__init__.py +++ b/git/compat.py @@ -24,6 +24,7 @@ Dict, IO, Optional, + Tuple, Type, Union, overload, @@ -92,16 +93,16 @@ def win_encode(s: Optional[AnyStr]) -> Optional[bytes]: return None -def with_metaclass(meta: Type[Any], *bases: Any) -> 'metaclass': # type: ignore ## mypy cannot understand dynamic class creation +def with_metaclass(meta: Type[Any], *bases: Any) -> TBD: # type: ignore ## mypy cannot understand dynamic class creation """copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15""" class metaclass(meta): # type: ignore __call__ = type.__call__ __init__ = type.__init__ # type: ignore - def __new__(cls, name: str, nbases: Optional[int], d: Dict[str, Any]) -> TBD: + def __new__(cls, name: str, nbases: Optional[Tuple[int, ...]], d: Dict[str, Any]) -> TBD: if nbases is None: return type.__new__(cls, name, (), d) return meta(name, bases, d) - return metaclass(meta.__name__ + 'Helper', None, {}) + return metaclass(meta.__name__ + 'Helper', None, {}) # type: ignore diff --git a/git/compat/typing.py b/git/compat/typing.py deleted file mode 100644 index 925c5ba2e..000000000 --- a/git/compat/typing.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# config.py -# Copyright (C) 2021 Michael Trier (mtrier@gmail.com) and contributors -# -# This module is part of GitPython and is released under -# the BSD License: http://www.opensource.org/licenses/bsd-license.php - -import sys - -if sys.version_info[:2] >= (3, 8): - from typing import Final, Literal # noqa: F401 -else: - from typing_extensions import Final, Literal # noqa: F401 diff --git a/git/config.py b/git/config.py index 0c8d975db..ea7302f4c 100644 --- a/git/config.py +++ b/git/config.py @@ -22,13 +22,23 @@ with_metaclass, is_win, ) -from git.compat.typing import Literal + from git.util import LockFile import os.path as osp import configparser as cp +# typing------------------------------------------------------- + +from typing import TYPE_CHECKING, Tuple + +from git.types import Literal + +if TYPE_CHECKING: + pass + +# ------------------------------------------------------------- __all__ = ('GitConfigParser', 'SectionConstraint') @@ -38,7 +48,8 @@ # invariants # represents the configuration level of a configuration file -CONFIG_LEVELS = ("system", "user", "global", "repository") +CONFIG_LEVELS = ("system", "user", "global", "repository" + ) # type: Tuple[Literal['system'], Literal['user'], Literal['global'], Literal['repository']] # Section pattern to detect conditional includes. # https://git-scm.com/docs/git-config#_conditional_includes diff --git a/git/diff.py b/git/diff.py index 943916ea8..5a7b189fc 100644 --- a/git/diff.py +++ b/git/diff.py @@ -16,8 +16,7 @@ # typing ------------------------------------------------------------------ from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union, TYPE_CHECKING -from git.compat.typing import Final, Literal -from git.types import TBD +from git.types import TBD, Final, Literal if TYPE_CHECKING: from .objects.tree import Tree diff --git a/git/remote.py b/git/remote.py index 2eeafcc41..e17f7bb8c 100644 --- a/git/remote.py +++ b/git/remote.py @@ -16,7 +16,7 @@ Iterable, IterableList, RemoteProgress, - CallableRemoteProgress + CallableRemoteProgress, ) from git.util import ( join_path, @@ -36,9 +36,9 @@ # typing------------------------------------------------------- -from typing import Any, Callable, Dict, Optional, TYPE_CHECKING, Union, cast, overload +from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union, cast, overload -from git.types import PathLike, Literal +from git.types import PathLike, Literal, TBD if TYPE_CHECKING: from git.repo.base import Repo @@ -59,7 +59,7 @@ #{ Utilities -def add_progress(kwargs: Any, git: Git, progress: Optional[Callable[..., Any]]) -> Any: +def add_progress(kwargs: Any, git: Git, progress: Union[Callable[..., Any], None]) -> Any: """Add the --progress flag to the given kwargs dict if supported by the git command. If the actual progress in the given progress instance is not given, we do not request any progress @@ -167,7 +167,7 @@ def remote_ref(self) -> Union[RemoteReference, TagReference]: # END @classmethod - def _from_line(cls, remote, line: str) -> 'PushInfo': + def _from_line(cls, remote: 'Remote', line: str) -> 'PushInfo': """Create a new PushInfo instance as parsed from line which is expected to be like refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes""" control_character, from_to, summary = line.split('\t', 3) @@ -276,7 +276,8 @@ def refresh(cls) -> Literal[True]: return True - def __init__(self, ref: SymbolicReference, flags: int, note: str = '', old_commit: Optional['Commit'] = None, + def __init__(self, ref: SymbolicReference, flags: int, note: str = '', + old_commit: Union['Commit', TagReference, 'Tree', 'Blob', None] = None, remote_ref_path: Optional[PathLike] = None) -> None: """ Initialize a new instance @@ -341,7 +342,7 @@ def _from_line(cls, repo: 'Repo', line: str, fetch_line: str) -> 'FetchInfo': # END control char exception handling # parse operation string for more info - makes no sense for symbolic refs, but we parse it anyway - old_commit = None + old_commit = None # type: Union[Commit, TagReference, Tree, Blob, None] is_tag_operation = False if 'rejected' in operation: flags |= cls.REJECTED @@ -433,7 +434,7 @@ class Remote(LazyMixin, Iterable): __slots__ = ("repo", "name", "_config_reader") _id_attribute_ = "name" - def __init__(self, repo, name): + def __init__(self, repo: 'Repo', name: str) -> None: """Initialize a remote instance :param repo: The repository we are a remote of @@ -441,7 +442,7 @@ def __init__(self, repo, name): self.repo = repo # type: 'Repo' self.name = name - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> Any: """Allows to call this instance like remote.special( \\*args, \\*\\*kwargs) to call git-remote special self.name""" if attr == "_config_reader": @@ -455,10 +456,10 @@ def __getattr__(self, attr): return super(Remote, self).__getattr__(attr) # END handle exception - def _config_section_name(self): + def _config_section_name(self) -> str: return 'remote "%s"' % self.name - def _set_cache_(self, attr): + def _set_cache_(self, attr: str) -> Any: if attr == "_config_reader": # NOTE: This is cached as __getattr__ is overridden to return remote config values implicitly, such as # in print(r.pushurl) @@ -466,22 +467,22 @@ def _set_cache_(self, attr): else: super(Remote, self)._set_cache_(attr) - def __str__(self): + def __str__(self) -> str: return self.name - def __repr__(self): + def __repr__(self) -> str: return '' % (self.__class__.__name__, self.name) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, type(self)) and self.name == other.name - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not (self == other) - def __hash__(self): + def __hash__(self) -> int: return hash(self.name) - def exists(self): + def exists(self) -> bool: """ :return: True if this is a valid, existing remote. Valid remotes have an entry in the repository's configuration""" @@ -496,7 +497,7 @@ def exists(self): # end @classmethod - def iter_items(cls, repo): + def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Iterator['Remote']: """:return: Iterator yielding Remote objects of the given repository""" for section in repo.config_reader("repository").sections(): if not section.startswith('remote '): @@ -508,7 +509,7 @@ def iter_items(cls, repo): yield Remote(repo, section[lbound + 1:rbound]) # END for each configuration section - def set_url(self, new_url, old_url=None, **kwargs): + def set_url(self, new_url: str, old_url: Optional[str] = None, **kwargs: Any) -> 'Remote': """Configure URLs on current remote (cf command git remote set_url) This command manages URLs on the remote. @@ -525,7 +526,7 @@ def set_url(self, new_url, old_url=None, **kwargs): self.repo.git.remote(scmd, self.name, new_url, **kwargs) return self - def add_url(self, url, **kwargs): + def add_url(self, url: str, **kwargs: Any) -> 'Remote': """Adds a new url on current remote (special case of git remote set_url) This command adds new URLs to a given remote, making it possible to have @@ -536,7 +537,7 @@ def add_url(self, url, **kwargs): """ return self.set_url(url, add=True) - def delete_url(self, url, **kwargs): + def delete_url(self, url: str, **kwargs: Any) -> 'Remote': """Deletes a new url on current remote (special case of git remote set_url) This command deletes new URLs to a given remote, making it possible to have @@ -548,10 +549,11 @@ def delete_url(self, url, **kwargs): return self.set_url(url, delete=True) @property - def urls(self): + def urls(self) -> Iterator[str]: """:return: Iterator yielding all configured URL targets on a remote as strings""" try: - remote_details = self.repo.git.remote("get-url", "--all", self.name) + # can replace cast with type assert? + remote_details = cast(str, self.repo.git.remote("get-url", "--all", self.name)) for line in remote_details.split('\n'): yield line except GitCommandError as ex: @@ -562,23 +564,23 @@ def urls(self): # if 'Unknown subcommand: get-url' in str(ex): try: - remote_details = self.repo.git.remote("show", self.name) + remote_details = cast(str, self.repo.git.remote("show", self.name)) 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']): + 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 = self.repo.git.config('--get-all', 'remote.%s.url' % self.name) + remote_details = cast(str, self.repo.git.config('--get-all', 'remote.%s.url' % self.name)) for line in remote_details.split('\n'): yield line else: - raise ex + raise _ex else: raise ex @property - def refs(self): + def refs(self) -> IterableList: """ :return: IterableList of RemoteReference objects. It is prefixed, allowing @@ -589,7 +591,7 @@ def refs(self): return out_refs @property - def stale_refs(self): + def stale_refs(self) -> IterableList: """ :return: IterableList RemoteReference objects that do not have a corresponding @@ -623,7 +625,7 @@ def stale_refs(self): return out_refs @classmethod - def create(cls, repo, name, url, **kwargs): + def create(cls, repo: 'Repo', name: str, url: str, **kwargs: Any) -> 'Remote': """Create a new remote to the given repository :param repo: Repository instance that is to receive the new remote :param name: Desired name of the remote @@ -640,7 +642,7 @@ def create(cls, repo, name, url, **kwargs): add = create @classmethod - def remove(cls, repo, name): + def remove(cls, repo: 'Repo', name: str) -> str: """Remove the remote with the given name :return: the passed remote name to remove """ @@ -652,7 +654,7 @@ def remove(cls, repo, name): # alias rm = remove - def rename(self, new_name): + def rename(self, new_name: str) -> 'Remote': """Rename self to the given new_name :return: self """ if self.name == new_name: @@ -664,7 +666,7 @@ def rename(self, new_name): return self - def update(self, **kwargs): + def update(self, **kwargs: Any) -> 'Remote': """Fetch all changes for this remote, including new branches which will be forced in ( in case your local remote branch is not part the new remote branches ancestry anymore ). @@ -678,7 +680,8 @@ def update(self, **kwargs): self.repo.git.remote(scmd, self.name, **kwargs) return self - def _get_fetch_info_from_stderr(self, proc, progress): + def _get_fetch_info_from_stderr(self, proc: TBD, + progress: Union[Callable[..., Any], RemoteProgress, None]) -> IterableList: progress = to_progress_instance(progress) # skip first line as it is some remote info we are not interested in @@ -737,7 +740,8 @@ def _get_fetch_info_from_stderr(self, proc, progress): log.warning("Git informed while fetching: %s", err_line.strip()) return output - def _get_push_info(self, proc, progress): + def _get_push_info(self, proc: TBD, + progress: Union[Callable[..., Any], RemoteProgress, None]) -> IterableList: progress = to_progress_instance(progress) # read progress information from stderr @@ -745,9 +749,9 @@ def _get_push_info(self, proc, progress): # 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 = [] + output = IterableList('push_infos') - def stdout_handler(line): + def stdout_handler(line: str) -> None: try: output.append(PushInfo._from_line(self, line)) except ValueError: @@ -766,7 +770,7 @@ def stdout_handler(line): return output - def _assert_refspec(self): + def _assert_refspec(self) -> None: """Turns out we can't deal with remotes if the refspec is missing""" config = self.config_reader unset = 'placeholder' @@ -779,7 +783,9 @@ def _assert_refspec(self): finally: config.release() - def fetch(self, refspec=None, progress=None, verbose=True, **kwargs): + def fetch(self, refspec: Union[str, List[str], None] = None, + progress: Union[Callable[..., Any], None] = None, + verbose: bool = True, **kwargs: Any) -> IterableList: """Fetch the latest changes for this remote :param refspec: @@ -810,9 +816,10 @@ def fetch(self, refspec=None, progress=None, verbose=True, **kwargs): if refspec is None: # No argument refspec, then ensure the repo's config has a fetch refspec. self._assert_refspec() + kwargs = add_progress(kwargs, self.repo.git, progress) if isinstance(refspec, list): - args = refspec + args = refspec # type: Sequence[Optional[str]] # should need this - check logic for passing None through else: args = [refspec] @@ -823,7 +830,9 @@ def fetch(self, refspec=None, progress=None, verbose=True, **kwargs): self.repo.odb.update_cache() return res - def pull(self, refspec=None, progress=None, **kwargs): + def pull(self, refspec: Union[str, List[str], None] = None, + progress: Union[Callable[..., Any], None] = None, + **kwargs: Any) -> IterableList: """Pull changes from the given branch, being the same as a fetch followed by a merge of branch with your local branch. @@ -842,7 +851,9 @@ def pull(self, refspec=None, progress=None, **kwargs): self.repo.odb.update_cache() return res - def push(self, refspec=None, progress=None, **kwargs): + def push(self, refspec: Union[str, List[str], None] = None, + progress: Union[Callable[..., Any], None] = None, + **kwargs: Any) -> IterableList: """Push changes from source branch in refspec to target branch in refspec. :param refspec: see 'fetch' method @@ -873,14 +884,14 @@ def push(self, refspec=None, progress=None, **kwargs): return self._get_push_info(proc, progress) @property - def config_reader(self): + def config_reader(self) -> SectionConstraint: """ :return: GitConfigParser compatible object able to read options for only our remote. Hence you may simple type config.get("pushurl") to obtain the information""" return self._config_reader - def _clear_cache(self): + def _clear_cache(self) -> None: try: del(self._config_reader) except AttributeError: @@ -888,7 +899,7 @@ def _clear_cache(self): # END handle exception @property - def config_writer(self): + def config_writer(self) -> SectionConstraint: """ :return: GitConfigParser compatible object able to write options for this remote. :note: diff --git a/git/repo/base.py b/git/repo/base.py index ed0a810e4..94c6e30b0 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -34,8 +34,7 @@ # typing ------------------------------------------------------ -from git.compat.typing import Literal -from git.types import TBD, PathLike +from git.types import TBD, PathLike, Literal from typing import (Any, BinaryIO, Callable, Dict, Iterator, List, Mapping, Optional, TextIO, Tuple, Type, Union, @@ -434,7 +433,7 @@ def delete_tag(self, *tags: TBD) -> None: """Delete the given tag references""" return TagReference.delete(self, *tags) - def create_remote(self, name: str, url: PathLike, **kwargs: Any) -> Remote: + def create_remote(self, name: str, url: str, **kwargs: Any) -> Remote: """Create a new remote. For more information, please see the documentation of the Remote.create @@ -443,7 +442,7 @@ def create_remote(self, name: str, url: PathLike, **kwargs: Any) -> Remote: :return: Remote reference""" return Remote.create(self, name, url, **kwargs) - def delete_remote(self, remote: 'Remote') -> Type['Remote']: + def delete_remote(self, remote: 'Remote') -> str: """Delete the given remote.""" return Remote.remove(self, remote) diff --git a/git/util.py b/git/util.py index af4990286..558be1e4d 100644 --- a/git/util.py +++ b/git/util.py @@ -21,8 +21,8 @@ # typing --------------------------------------------------------- -from typing import (Any, AnyStr, BinaryIO, Callable, Dict, Generator, IO, List, - NoReturn, Optional, Pattern, Sequence, Tuple, Union, cast, TYPE_CHECKING) +from typing import (Any, AnyStr, BinaryIO, Callable, Dict, Generator, IO, Iterator, List, + Optional, Pattern, Sequence, Tuple, Union, cast, TYPE_CHECKING) if TYPE_CHECKING: from git.remote import Remote from git.repo.base import Repo @@ -996,7 +996,8 @@ def list_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> 'IterableList': return out_list @classmethod - def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> NoReturn: + def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Iterator[TBD]: + # 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") diff --git a/mypy.ini b/mypy.ini index b63d68fd3..8f86a6af7 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,7 +2,7 @@ [mypy] # TODO: enable when we've fully annotated everything -#disallow_untyped_defs = True +# disallow_untyped_defs = True # TODO: remove when 'gitdb' is fully annotated [mypy-gitdb.*]