diff --git a/.flake8 b/.flake8 index 3cf342f93..2f9e6ed27 100644 --- a/.flake8 +++ b/.flake8 @@ -20,7 +20,7 @@ ignore = E265,E266,E731,E704, W293, W504, ANN0 ANN1 ANN2, TC002, - # TC0, TC1, TC2 + TC0, TC1, TC2 # B, A, D, diff --git a/git/config.py b/git/config.py index 345cb40e6..c4b26ba63 100644 --- a/git/config.py +++ b/git/config.py @@ -6,6 +6,7 @@ """Module containing module parser implementation able to properly read and write configuration files""" +import sys import abc from functools import wraps import inspect @@ -14,12 +15,10 @@ import os import re import fnmatch -from collections import OrderedDict from git.compat import ( defenc, force_text, - with_metaclass, is_win, ) @@ -31,15 +30,24 @@ # typing------------------------------------------------------- -from typing import (Any, Callable, IO, List, Dict, Sequence, - TYPE_CHECKING, Tuple, Union, cast, overload) +from typing import (Any, Callable, Generic, IO, List, Dict, Sequence, + TYPE_CHECKING, Tuple, TypeVar, Union, cast, overload) -from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, TBD, assert_never, is_config_level +from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, TBD, assert_never, _T if TYPE_CHECKING: from git.repo.base import Repo from io import BytesIO +T_ConfigParser = TypeVar('T_ConfigParser', bound='GitConfigParser') + +if sys.version_info[:2] < (3, 7): + from collections import OrderedDict + OrderedDict_OMD = OrderedDict +else: + from typing import OrderedDict + OrderedDict_OMD = OrderedDict[str, List[_T]] + # ------------------------------------------------------------- __all__ = ('GitConfigParser', 'SectionConstraint') @@ -61,7 +69,6 @@ class MetaParserBuilder(abc.ABCMeta): - """Utlity class wrapping base-class methods into decorators that assure read-only properties""" def __new__(cls, name: str, bases: TBD, clsdict: Dict[str, Any]) -> TBD: """ @@ -115,7 +122,7 @@ def flush_changes(self, *args: Any, **kwargs: Any) -> Any: return flush_changes -class SectionConstraint(object): +class SectionConstraint(Generic[T_ConfigParser]): """Constrains a ConfigParser to only option commands which are constrained to always use the section we have been initialized with. @@ -128,7 +135,7 @@ class SectionConstraint(object): _valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option", "remove_section", "remove_option", "options") - def __init__(self, config: 'GitConfigParser', section: str) -> None: + def __init__(self, config: T_ConfigParser, section: str) -> None: self._config = config self._section_name = section @@ -149,7 +156,7 @@ def _call_config(self, method: str, *args: Any, **kwargs: Any) -> Any: return getattr(self._config, method)(self._section_name, *args, **kwargs) @property - def config(self) -> 'GitConfigParser': + def config(self) -> T_ConfigParser: """return: Configparser instance we constrain""" return self._config @@ -157,7 +164,7 @@ def release(self) -> None: """Equivalent to GitConfigParser.release(), which is called on our underlying parser instance""" return self._config.release() - def __enter__(self) -> 'SectionConstraint': + def __enter__(self) -> 'SectionConstraint[T_ConfigParser]': self._config.__enter__() return self @@ -165,10 +172,10 @@ def __exit__(self, exception_type: str, exception_value: str, traceback: str) -> self._config.__exit__(exception_type, exception_value, traceback) -class _OMD(OrderedDict): +class _OMD(OrderedDict_OMD): """Ordered multi-dict.""" - def __setitem__(self, key: str, value: Any) -> None: + def __setitem__(self, key: str, value: _T) -> None: # type: ignore[override] super(_OMD, self).__setitem__(key, [value]) def add(self, key: str, value: Any) -> None: @@ -177,7 +184,7 @@ def add(self, key: str, value: Any) -> None: return None super(_OMD, self).__getitem__(key).append(value) - def setall(self, key: str, values: Any) -> None: + def setall(self, key: str, values: List[_T]) -> None: super(_OMD, self).__setitem__(key, values) def __getitem__(self, key: str) -> Any: @@ -194,25 +201,17 @@ def setlast(self, key: str, value: Any) -> None: prior = super(_OMD, self).__getitem__(key) prior[-1] = value - @overload - def get(self, key: str, default: None = ...) -> None: - ... - - @overload - def get(self, key: str, default: Any = ...) -> Any: - ... - - def get(self, key: str, default: Union[Any, None] = None) -> Union[Any, None]: - return super(_OMD, self).get(key, [default])[-1] + def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]: # type: ignore + return super(_OMD, self).get(key, [default])[-1] # type: ignore - def getall(self, key: str) -> Any: + def getall(self, key: str) -> List[_T]: return super(_OMD, self).__getitem__(key) - def items(self) -> List[Tuple[str, Any]]: # type: ignore[override] + def items(self) -> List[Tuple[str, _T]]: # type: ignore[override] """List of (key, last value for key).""" return [(k, self[k]) for k in self] - def items_all(self) -> List[Tuple[str, List[Any]]]: + def items_all(self) -> List[Tuple[str, List[_T]]]: """List of (key, list of values for key).""" return [(k, self.getall(k)) for k in self] @@ -238,7 +237,7 @@ def get_config_path(config_level: Lit_config_levels) -> str: assert_never(config_level, ValueError(f"Invalid configuration level: {config_level!r}")) -class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser)): # type: ignore ## mypy does not understand dynamic class creation # noqa: E501 +class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder): """Implements specifics required to read git style configuration files. @@ -298,7 +297,10 @@ def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Unio :param repo: Reference to repository to use if [includeIf] sections are found in configuration files. """ - cp.RawConfigParser.__init__(self, dict_type=_OMD) + cp.RawConfigParser.__init__(self, dict_type=_OMD) # type: ignore[arg-type] + self._dict: Callable[..., _OMD] # type: ignore[assignment] # mypy/typeshed bug + self._defaults: _OMD # type: ignore[assignment] # mypy/typeshed bug + self._sections: _OMD # type: ignore[assignment] # mypy/typeshed bug # Used in python 3, needs to stay in sync with sections for underlying implementation to work if not hasattr(self, '_proxies'): @@ -309,9 +311,9 @@ def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Unio else: if config_level is None: if read_only: - self._file_or_files = [get_config_path(f) + self._file_or_files = [get_config_path(cast(Lit_config_levels, f)) for f in CONFIG_LEVELS - if is_config_level(f) and f != 'repository'] + if f != 'repository'] else: raise ValueError("No configuration level or configuration files specified") else: @@ -424,7 +426,7 @@ def string_decode(v: str) -> str: # is it a section header? mo = self.SECTCRE.match(line.strip()) if not is_multi_line and mo: - sectname = mo.group('header').strip() + sectname: str = mo.group('header').strip() if sectname in self._sections: cursect = self._sections[sectname] elif sectname == cp.DEFAULTSECT: @@ -535,7 +537,7 @@ def _included_paths(self) -> List[Tuple[str, str]]: return paths - def read(self) -> None: + def read(self) -> None: # type: ignore[override] """Reads the data stored in the files we have been initialized with. It will ignore files that cannot be read, possibly leaving an empty configuration @@ -623,10 +625,11 @@ def write_section(name, section_dict): if self._defaults: write_section(cp.DEFAULTSECT, self._defaults) + value: TBD for name, value in self._sections.items(): write_section(name, value) - def items(self, section_name: str) -> List[Tuple[str, str]]: + def items(self, section_name: str) -> List[Tuple[str, str]]: # type: ignore[override] """:return: list((option, value), ...) pairs of all items in the given section""" return [(k, v) for k, v in super(GitConfigParser, self).items(section_name) if k != '__name__'] diff --git a/git/diff.py b/git/diff.py index 98a5cfd97..74ca0b64d 100644 --- a/git/diff.py +++ b/git/diff.py @@ -15,8 +15,8 @@ # typing ------------------------------------------------------------------ -from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING -from git.types import PathLike, TBD, Literal, TypeGuard +from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast +from git.types import PathLike, TBD, Literal if TYPE_CHECKING: from .objects.tree import Tree @@ -28,9 +28,9 @@ Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T', 'U'] -def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: - # return True - return inp in ['A', 'D', 'C', 'M', 'R', 'T', 'U'] +# def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: +# # return True +# return inp in ['A', 'D', 'C', 'M', 'R', 'T', 'U'] # ------------------------------------------------------------------------ @@ -517,8 +517,8 @@ def _handle_diff_line(lines_bytes: bytes, repo: 'Repo', index: DiffIndex) -> Non # Change type can be R100 # R: status letter # 100: score (in case of copy and rename) - assert is_change_type(_change_type[0]), f"Unexpected value for change_type received: {_change_type[0]}" - change_type: Lit_change_type = _change_type[0] + # assert is_change_type(_change_type[0]), f"Unexpected value for change_type received: {_change_type[0]}" + change_type: Lit_change_type = cast(Lit_change_type, _change_type[0]) score_str = ''.join(_change_type[1:]) score = int(score_str) if score_str.isdigit() else None path = path.strip() diff --git a/git/index/fun.py b/git/index/fun.py index e071e15cf..49e3f2c52 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -53,10 +53,11 @@ from typing import (Dict, IO, List, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast) -from git.types import PathLike, TypeGuard +from git.types import PathLike if TYPE_CHECKING: from .base import IndexFile + from git.db import GitCmdObjectDB from git.objects.tree import TreeCacheTup # from git.objects.fun import EntryTupOrNone @@ -149,15 +150,15 @@ def write_cache(entries: Sequence[Union[BaseIndexEntry, 'IndexEntry']], stream: # body for entry in entries: beginoffset = tell() - write(entry[4]) # ctime - write(entry[5]) # mtime - path_str: str = entry[3] + write(entry.ctime_bytes) # ctime + write(entry.mtime_bytes) # mtime + path_str = str(entry.path) path: bytes = force_bytes(path_str, encoding=defenc) plen = len(path) & CE_NAMEMASK # path length - assert plen == len(path), "Path %s too long to fit into index" % entry[3] - flags = plen | (entry[2] & CE_NAMEMASK_INV) # clear possible previous values - write(pack(">LLLLLL20sH", entry[6], entry[7], entry[0], - entry[8], entry[9], entry[10], entry[1], flags)) + assert plen == len(path), "Path %s too long to fit into index" % entry.path + flags = plen | (entry.flags & CE_NAMEMASK_INV) # clear possible previous values + write(pack(">LLLLLL20sH", entry.dev, entry.inode, entry.mode, + entry.uid, entry.gid, entry.size, entry.binsha, flags)) write(path) real_size = ((tell() - beginoffset + 8) & ~7) write(b"\0" * ((beginoffset + real_size) - tell())) @@ -188,15 +189,16 @@ def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, i """: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_key_tup(entry_key: Tuple) -> TypeGuard[Tuple[PathLike, int]]: - return isinstance(entry_key, tuple) and len(entry_key) == 2 + # def is_entry_key_tup(entry_key: Tuple) -> TypeGuard[Tuple[PathLike, int]]: + # return isinstance(entry_key, tuple) and len(entry_key) == 2 if len(entry) == 1: entry_first = entry[0] assert isinstance(entry_first, BaseIndexEntry) return (entry_first.path, entry_first.stage) else: - assert is_entry_key_tup(entry) + # assert is_entry_key_tup(entry) + entry = cast(Tuple[PathLike, int], entry) return entry # END handle entry @@ -244,7 +246,7 @@ def read_cache(stream: IO[bytes]) -> Tuple[int, Dict[Tuple[PathLike, int], 'Inde content_sha = extension_data[-20:] # truncate the sha in the end as we will dynamically create it anyway - extension_data = extension_data[:-20] + extension_data = extension_data[: -20] return (version, entries, extension_data, content_sha) @@ -310,7 +312,7 @@ def _tree_entry_to_baseindexentry(tree_entry: 'TreeCacheTup', stage: int) -> Bas return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2])) -def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]: +def aggressive_tree_merge(odb: 'GitCmdObjectDB', tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]: """ :return: list of BaseIndexEntries representing the aggressive merge of the given trees. All valid entries are on stage 0, whereas the conflicting ones are left diff --git a/git/index/typ.py b/git/index/typ.py index bb1a03845..46f1b0779 100644 --- a/git/index/typ.py +++ b/git/index/typ.py @@ -11,7 +11,7 @@ # typing ---------------------------------------------------------------------- -from typing import (List, Sequence, TYPE_CHECKING, Tuple, cast) +from typing import (NamedTuple, Sequence, TYPE_CHECKING, Tuple, Union, cast) from git.types import PathLike @@ -59,14 +59,37 @@ def __call__(self, stage_blob: Blob) -> bool: return False -class BaseIndexEntry(tuple): +class BaseIndexEntryHelper(NamedTuple): + """Typed namedtuple to provide named attribute access for BaseIndexEntry. + Needed to allow overriding __new__ in child class to preserve backwards compat.""" + mode: int + binsha: bytes + flags: int + path: PathLike + ctime_bytes: bytes = pack(">LL", 0, 0) + mtime_bytes: bytes = pack(">LL", 0, 0) + dev: int = 0 + inode: int = 0 + uid: int = 0 + gid: int = 0 + size: int = 0 + + +class BaseIndexEntry(BaseIndexEntryHelper): """Small Brother of an index entry which can be created to describe changes done to the index in which case plenty of additional information is not required. As the first 4 data members match exactly to the IndexEntry type, methods expecting a BaseIndexEntry can also handle full IndexEntries even if they - use numeric indices for performance reasons. """ + use numeric indices for performance reasons. + """ + + def __new__(cls, inp_tuple: Union[Tuple[int, bytes, int, PathLike], + Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int]] + ) -> 'BaseIndexEntry': + """Override __new__ to allow construction from a tuple for backwards compatibility """ + return super().__new__(cls, *inp_tuple) def __str__(self) -> str: return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path) @@ -74,20 +97,10 @@ def __str__(self) -> str: def __repr__(self) -> str: return "(%o, %s, %i, %s)" % (self.mode, self.hexsha, self.stage, self.path) - @property - def mode(self) -> int: - """ File Mode, compatible to stat module constants """ - return self[0] - - @property - def binsha(self) -> bytes: - """binary sha of the blob """ - return self[1] - @property def hexsha(self) -> str: """hex version of our sha""" - return b2a_hex(self[1]).decode('ascii') + return b2a_hex(self.binsha).decode('ascii') @property def stage(self) -> int: @@ -100,17 +113,7 @@ def stage(self) -> int: :note: For more information, see http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html """ - return (self[2] & CE_STAGEMASK) >> CE_STAGESHIFT - - @property - def path(self) -> str: - """:return: our path relative to the repository working tree root""" - return self[3] - - @property - def flags(self) -> List[str]: - """:return: flags stored with this entry""" - return self[2] + return (self.flags & CE_STAGEMASK) >> CE_STAGESHIFT @classmethod def from_blob(cls, blob: Blob, stage: int = 0) -> 'BaseIndexEntry': @@ -136,40 +139,15 @@ def ctime(self) -> Tuple[int, int]: :return: Tuple(int_time_seconds_since_epoch, int_nano_seconds) of the file's creation time""" - return cast(Tuple[int, int], unpack(">LL", self[4])) + return cast(Tuple[int, int], unpack(">LL", self.ctime_bytes)) @property def mtime(self) -> Tuple[int, int]: """See ctime property, but returns modification time """ - return cast(Tuple[int, int], unpack(">LL", self[5])) - - @property - def dev(self) -> int: - """ Device ID """ - return self[6] - - @property - def inode(self) -> int: - """ Inode ID """ - return self[7] - - @property - def uid(self) -> int: - """ User ID """ - return self[8] - - @property - def gid(self) -> int: - """ Group ID """ - return self[9] - - @property - def size(self) -> int: - """:return: Uncompressed size of the blob """ - return self[10] + return cast(Tuple[int, int], unpack(">LL", self.mtime_bytes)) @classmethod - def from_base(cls, base): + def from_base(cls, base: 'BaseIndexEntry') -> 'IndexEntry': """ :return: Minimal entry as created from the given BaseIndexEntry instance. diff --git a/git/objects/commit.py b/git/objects/commit.py index 11cf52a5e..884f65228 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -39,9 +39,9 @@ # typing ------------------------------------------------------------------ -from typing import Any, IO, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING +from typing import Any, IO, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING, cast -from git.types import PathLike, TypeGuard, Literal +from git.types import PathLike, Literal if TYPE_CHECKING: from git.repo import Repo @@ -323,16 +323,18 @@ def _iter_from_process_or_stream(cls, repo: 'Repo', proc_or_stream: Union[Popen, :param proc: git-rev-list process instance - one sha per line :return: iterator returning Commit objects""" - def is_proc(inp) -> TypeGuard[Popen]: - return hasattr(proc_or_stream, 'wait') and not hasattr(proc_or_stream, 'readline') + # def is_proc(inp) -> TypeGuard[Popen]: + # return hasattr(proc_or_stream, 'wait') and not hasattr(proc_or_stream, 'readline') - def is_stream(inp) -> TypeGuard[IO]: - return hasattr(proc_or_stream, 'readline') + # def is_stream(inp) -> TypeGuard[IO]: + # return hasattr(proc_or_stream, 'readline') - if is_proc(proc_or_stream): + if hasattr(proc_or_stream, 'wait'): + proc_or_stream = cast(Popen, proc_or_stream) if proc_or_stream.stdout is not None: stream = proc_or_stream.stdout - elif is_stream(proc_or_stream): + elif hasattr(proc_or_stream, 'readline'): + proc_or_stream = cast(IO, proc_or_stream) stream = proc_or_stream readline = stream.readline @@ -351,7 +353,8 @@ def is_stream(inp) -> TypeGuard[IO]: # END for each line in stream # TODO: Review this - it seems process handling got a bit out of control # due to many developers trying to fix the open file handles issue - if is_proc(proc_or_stream): + if hasattr(proc_or_stream, 'wait'): + proc_or_stream = cast(Popen, proc_or_stream) finalize_process(proc_or_stream) @ classmethod diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index d5ba118f6..29212167c 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -965,13 +965,12 @@ def remove(self, module: bool = True, force: bool = False, # now git config - need the config intact, otherwise we can't query # information anymore - writer: Union[GitConfigParser, SectionConstraint] - with self.repo.config_writer() as writer: - writer.remove_section(sm_section(self.name)) + with self.repo.config_writer() as gcp_writer: + gcp_writer.remove_section(sm_section(self.name)) - with self.config_writer() as writer: - writer.remove_section() + with self.config_writer() as sc_writer: + sc_writer.remove_section() # END delete configuration return self @@ -1024,7 +1023,8 @@ def set_parent_commit(self, commit: Union[Commit_ish, None], check: bool = True) return self @unbare_repo - def config_writer(self, index: Union['IndexFile', None] = None, write: bool = True) -> SectionConstraint: + def config_writer(self, index: Union['IndexFile', None] = None, write: bool = True + ) -> SectionConstraint['SubmoduleConfigParser']: """:return: a config writer instance allowing you to read and write the data belonging to this submodule into the .gitmodules file. @@ -1201,7 +1201,7 @@ def name(self) -> str: """ return self._name - def config_reader(self) -> SectionConstraint: + def config_reader(self) -> SectionConstraint[SubmoduleConfigParser]: """ :return: ConfigReader instance which allows you to qurey the configuration values of this submodule, as provided by the .gitmodules file diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index a776af889..cc1cd60a2 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -100,7 +100,7 @@ def flush_to_index(self) -> None: #} END interface #{ Overridden Methods - def write(self) -> None: + def write(self) -> None: # type: ignore[override] rval: None = super(SubmoduleConfigParser, self).write() self.flush_to_index() return rval diff --git a/git/objects/tree.py b/git/objects/tree.py index dd1fe7832..70f36af5d 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -24,7 +24,7 @@ from typing import (Any, Callable, Dict, Iterable, Iterator, List, Tuple, Type, Union, cast, TYPE_CHECKING) -from git.types import PathLike, TypeGuard, Literal +from git.types import PathLike, Literal if TYPE_CHECKING: from git.repo import Repo @@ -36,8 +36,8 @@ Tuple['Submodule', 'Submodule']]] -def is_tree_cache(inp: Tuple[bytes, int, str]) -> TypeGuard[TreeCacheTup]: - return isinstance(inp[0], bytes) and isinstance(inp[1], int) and isinstance([inp], str) +# def is_tree_cache(inp: Tuple[bytes, int, str]) -> TypeGuard[TreeCacheTup]: +# return isinstance(inp[0], bytes) and isinstance(inp[1], int) and isinstance([inp], str) #-------------------------------------------------------- diff --git a/git/refs/reference.py b/git/refs/reference.py index f584bb54d..646622816 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -8,7 +8,7 @@ # typing ------------------------------------------------------------------ from typing import Any, Callable, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING # NOQA -from git.types import Commit_ish, PathLike, TBD, Literal, TypeGuard, _T # NOQA +from git.types import Commit_ish, PathLike, TBD, Literal, _T # NOQA if TYPE_CHECKING: from git.repo import Repo diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 426d40d44..0e9dad5cc 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -24,7 +24,7 @@ # typing ------------------------------------------------------------------ from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING # NOQA -from git.types import Commit_ish, PathLike, TBD, Literal, TypeGuard # NOQA +from git.types import Commit_ish, PathLike, TBD, Literal # NOQA if TYPE_CHECKING: from git.repo import Repo diff --git a/git/remote.py b/git/remote.py index d903552f8..11007cb68 100644 --- a/git/remote.py +++ b/git/remote.py @@ -23,6 +23,7 @@ ) from .config import ( + GitConfigParser, SectionConstraint, cp, ) @@ -37,9 +38,9 @@ # typing------------------------------------------------------- from typing import (Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence, # NOQA[TC002] - TYPE_CHECKING, Type, Union, overload) + TYPE_CHECKING, Type, Union, cast, overload) -from git.types import PathLike, Literal, TBD, TypeGuard, Commit_ish # NOQA[TC002] +from git.types import PathLike, Literal, TBD, Commit_ish # NOQA[TC002] if TYPE_CHECKING: from git.repo.base import Repo @@ -50,8 +51,8 @@ flagKeyLiteral = Literal[' ', '!', '+', '-', '*', '=', 't', '?'] -def is_flagKeyLiteral(inp: str) -> TypeGuard[flagKeyLiteral]: - return inp in [' ', '!', '+', '-', '=', '*', 't', '?'] +# def is_flagKeyLiteral(inp: str) -> TypeGuard[flagKeyLiteral]: +# return inp in [' ', '!', '+', '-', '=', '*', 't', '?'] # ------------------------------------------------------------- @@ -342,8 +343,8 @@ def _from_line(cls, repo: 'Repo', line: str, fetch_line: str) -> 'FetchInfo': # parse lines remote_local_ref_str: str control_character, operation, local_remote_ref, remote_local_ref_str, note = match.groups() - assert is_flagKeyLiteral(control_character), f"{control_character}" - + # assert is_flagKeyLiteral(control_character), f"{control_character}" + control_character = cast(flagKeyLiteral, control_character) try: _new_hex_sha, _fetch_operation, fetch_note = fetch_line.split("\t") ref_type_name, fetch_note = fetch_note.split(' ', 1) @@ -911,7 +912,7 @@ def push(self, refspec: Union[str, List[str], None] = None, return self._get_push_info(proc, progress) @ property - def config_reader(self) -> SectionConstraint: + def config_reader(self) -> SectionConstraint[GitConfigParser]: """ :return: GitConfigParser compatible object able to read options for only our remote. diff --git a/git/repo/base.py b/git/repo/base.py index 64f32bd38..a57172c6c 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -36,7 +36,7 @@ # typing ------------------------------------------------------ -from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish, is_config_level +from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish from typing import (Any, BinaryIO, Callable, Dict, Iterator, List, Mapping, Optional, Sequence, TextIO, Tuple, Type, Union, @@ -482,7 +482,8 @@ def _get_config_path(self, config_level: Lit_config_levels) -> str: raise ValueError("Invalid configuration level: %r" % config_level) - def config_reader(self, config_level: Optional[Lit_config_levels] = None) -> GitConfigParser: + def config_reader(self, config_level: Optional[Lit_config_levels] = None + ) -> GitConfigParser: """ :return: GitConfigParser allowing to read the full git configuration, but not to write it @@ -498,12 +499,14 @@ def config_reader(self, config_level: Optional[Lit_config_levels] = None) -> Git unknown, instead the global path will be used.""" files = None if config_level is None: - files = [self._get_config_path(f) for f in self.config_level if is_config_level(f)] + files = [self._get_config_path(cast(Lit_config_levels, f)) + for f in self.config_level if cast(Lit_config_levels, f)] else: files = [self._get_config_path(config_level)] return GitConfigParser(files, read_only=True, repo=self) - def config_writer(self, config_level: Lit_config_levels = "repository") -> GitConfigParser: + def config_writer(self, config_level: Lit_config_levels = "repository" + ) -> GitConfigParser: """ :return: GitConfigParser allowing to write values of the specified configuration file level. diff --git a/git/types.py b/git/types.py index 53f0f1e4e..ccaffef3e 100644 --- a/git/types.py +++ b/git/types.py @@ -10,12 +10,13 @@ if sys.version_info[:2] >= (3, 8): from typing import Final, Literal, SupportsIndex, TypedDict, Protocol, runtime_checkable # noqa: F401 else: - from typing_extensions import Final, Literal, SupportsIndex, TypedDict, Protocol, runtime_checkable # noqa: F401 + from typing_extensions import (Final, Literal, SupportsIndex, # noqa: F401 + TypedDict, Protocol, runtime_checkable) # 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, 10): +# from typing import TypeGuard # noqa: F401 +# else: +# from typing_extensions import TypeGuard # noqa: F401 if sys.version_info[:2] < (3, 9): @@ -41,9 +42,9 @@ Lit_config_levels = Literal['system', 'global', 'user', 'repository'] -def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: - # return inp in get_args(Lit_config_level) # only py >= 3.8 - return inp in ("system", "user", "global", "repository") +# def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: +# # return inp in get_args(Lit_config_level) # only py >= 3.8 +# return inp in ("system", "user", "global", "repository") ConfigLevels_Tup = Tuple[Literal['system'], Literal['user'], Literal['global'], Literal['repository']] diff --git a/requirements.txt b/requirements.txt index 80d6b1b44..a20310fb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ gitdb>=4.0.1,<5 -typing-extensions>=3.10.0.0;python_version<"3.10" +typing-extensions>=3.7.4.3;python_version<"3.10"