Skip to content

Commit

Permalink
Rmv with_metaclass shim, make section constraint generic wrt its conf…
Browse files Browse the repository at this point in the history
…igparser type
  • Loading branch information
Yobmod committed Jul 24, 2021
1 parent 2bc2ac0 commit d812818
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ ignore = E265,E266,E731,E704,
W293, W504,
ANN0 ANN1 ANN2,
TC002,
# TC0, TC1, TC2
TC0, TC1, TC2
# B,
A,
D,
Expand Down
16 changes: 0 additions & 16 deletions git/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,3 @@ def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:
elif s is not None:
raise TypeError('Expected bytes or text, but got %r' % (s,))
return None


# type: ignore ## mypy cannot understand dynamic class creation
def with_metaclass(meta: Type[Any], *bases: Any) -> TBD:
"""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[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, {}) # type: ignore
55 changes: 24 additions & 31 deletions git/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
import os
import re
import fnmatch
from collections import OrderedDict

from git.compat import (
defenc,
force_text,
with_metaclass,
is_win,
)

Expand All @@ -31,15 +29,16 @@

# 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
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')
# -------------------------------------------------------------

__all__ = ('GitConfigParser', 'SectionConstraint')
Expand All @@ -61,7 +60,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:
"""
Expand Down Expand Up @@ -115,7 +113,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.
Expand All @@ -128,7 +126,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

Expand All @@ -149,26 +147,26 @@ 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

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

def __exit__(self, exception_type: str, exception_value: str, traceback: str) -> None:
self._config.__exit__(exception_type, exception_value, traceback)


class _OMD(OrderedDict):
class _OMD(Dict[str, List[_T]]):
"""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:
Expand All @@ -177,7 +175,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:
Expand All @@ -194,25 +192,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]

Expand All @@ -238,7 +228,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.
Expand Down Expand Up @@ -298,7 +288,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'):
Expand Down Expand Up @@ -424,7 +417,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:
Expand Down Expand Up @@ -535,7 +528,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
Expand Down Expand Up @@ -626,7 +619,7 @@ def write_section(name, section_dict):
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__']

Expand Down
3 changes: 2 additions & 1 deletion git/index/typ.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ class BaseIndexEntry(BaseIndexEntryHelper):
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]]
Expand Down
14 changes: 7 additions & 7 deletions git/objects/submodule/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion git/objects/submodule/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion git/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)

from .config import (
GitConfigParser,
SectionConstraint,
cp,
)
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 4 additions & 2 deletions git/repo/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -504,7 +505,8 @@ def config_reader(self, config_level: Optional[Lit_config_levels] = None) -> Git
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.
Expand Down

0 comments on commit d812818

Please sign in to comment.