66"""Module containing module parser implementation able to properly read and write
77configuration files"""
88
9+ import sys
910import abc
1011from functools import wraps
1112import inspect
1415import os
1516import re
1617import fnmatch
17- from collections import OrderedDict
1818
1919from git .compat import (
2020 defenc ,
2121 force_text ,
22- with_metaclass ,
2322 is_win ,
2423)
2524
3130
3231# typing-------------------------------------------------------
3332
34- from typing import (Any , Callable , IO , List , Dict , Sequence ,
35- TYPE_CHECKING , Tuple , Union , cast , overload )
33+ from typing import (Any , Callable , Generic , IO , List , Dict , Sequence ,
34+ TYPE_CHECKING , Tuple , TypeVar , Union , cast , overload )
3635
37- from git .types import Lit_config_levels , ConfigLevels_Tup , PathLike , TBD , assert_never , is_config_level
36+ from git .types import Lit_config_levels , ConfigLevels_Tup , PathLike , TBD , assert_never , _T
3837
3938if TYPE_CHECKING :
4039 from git .repo .base import Repo
4140 from io import BytesIO
4241
42+ T_ConfigParser = TypeVar ('T_ConfigParser' , bound = 'GitConfigParser' )
43+
44+ if sys .version_info [:2 ] < (3 , 7 ):
45+ from collections import OrderedDict
46+ OrderedDict_OMD = OrderedDict
47+ else :
48+ from typing import OrderedDict
49+ OrderedDict_OMD = OrderedDict [str , List [_T ]]
50+
4351# -------------------------------------------------------------
4452
4553__all__ = ('GitConfigParser' , 'SectionConstraint' )
6169
6270
6371class MetaParserBuilder (abc .ABCMeta ):
64-
6572 """Utlity class wrapping base-class methods into decorators that assure read-only properties"""
6673 def __new__ (cls , name : str , bases : TBD , clsdict : Dict [str , Any ]) -> TBD :
6774 """
@@ -115,7 +122,7 @@ def flush_changes(self, *args: Any, **kwargs: Any) -> Any:
115122 return flush_changes
116123
117124
118- class SectionConstraint (object ):
125+ class SectionConstraint (Generic [ T_ConfigParser ] ):
119126
120127 """Constrains a ConfigParser to only option commands which are constrained to
121128 always use the section we have been initialized with.
@@ -128,7 +135,7 @@ class SectionConstraint(object):
128135 _valid_attrs_ = ("get_value" , "set_value" , "get" , "set" , "getint" , "getfloat" , "getboolean" , "has_option" ,
129136 "remove_section" , "remove_option" , "options" )
130137
131- def __init__ (self , config : 'GitConfigParser' , section : str ) -> None :
138+ def __init__ (self , config : T_ConfigParser , section : str ) -> None :
132139 self ._config = config
133140 self ._section_name = section
134141
@@ -149,26 +156,26 @@ def _call_config(self, method: str, *args: Any, **kwargs: Any) -> Any:
149156 return getattr (self ._config , method )(self ._section_name , * args , ** kwargs )
150157
151158 @property
152- def config (self ) -> 'GitConfigParser' :
159+ def config (self ) -> T_ConfigParser :
153160 """return: Configparser instance we constrain"""
154161 return self ._config
155162
156163 def release (self ) -> None :
157164 """Equivalent to GitConfigParser.release(), which is called on our underlying parser instance"""
158165 return self ._config .release ()
159166
160- def __enter__ (self ) -> 'SectionConstraint' :
167+ def __enter__ (self ) -> 'SectionConstraint[T_ConfigParser] ' :
161168 self ._config .__enter__ ()
162169 return self
163170
164171 def __exit__ (self , exception_type : str , exception_value : str , traceback : str ) -> None :
165172 self ._config .__exit__ (exception_type , exception_value , traceback )
166173
167174
168- class _OMD (OrderedDict ):
175+ class _OMD (OrderedDict_OMD ):
169176 """Ordered multi-dict."""
170177
171- def __setitem__ (self , key : str , value : Any ) -> None :
178+ def __setitem__ (self , key : str , value : _T ) -> None : # type: ignore[override]
172179 super (_OMD , self ).__setitem__ (key , [value ])
173180
174181 def add (self , key : str , value : Any ) -> None :
@@ -177,7 +184,7 @@ def add(self, key: str, value: Any) -> None:
177184 return None
178185 super (_OMD , self ).__getitem__ (key ).append (value )
179186
180- def setall (self , key : str , values : Any ) -> None :
187+ def setall (self , key : str , values : List [ _T ] ) -> None :
181188 super (_OMD , self ).__setitem__ (key , values )
182189
183190 def __getitem__ (self , key : str ) -> Any :
@@ -194,25 +201,17 @@ def setlast(self, key: str, value: Any) -> None:
194201 prior = super (_OMD , self ).__getitem__ (key )
195202 prior [- 1 ] = value
196203
197- @overload
198- def get (self , key : str , default : None = ...) -> None :
199- ...
200-
201- @overload
202- def get (self , key : str , default : Any = ...) -> Any :
203- ...
204-
205- def get (self , key : str , default : Union [Any , None ] = None ) -> Union [Any , None ]:
206- return super (_OMD , self ).get (key , [default ])[- 1 ]
204+ def get (self , key : str , default : Union [_T , None ] = None ) -> Union [_T , None ]: # type: ignore
205+ return super (_OMD , self ).get (key , [default ])[- 1 ] # type: ignore
207206
208- def getall (self , key : str ) -> Any :
207+ def getall (self , key : str ) -> List [ _T ] :
209208 return super (_OMD , self ).__getitem__ (key )
210209
211- def items (self ) -> List [Tuple [str , Any ]]: # type: ignore[override]
210+ def items (self ) -> List [Tuple [str , _T ]]: # type: ignore[override]
212211 """List of (key, last value for key)."""
213212 return [(k , self [k ]) for k in self ]
214213
215- def items_all (self ) -> List [Tuple [str , List [Any ]]]:
214+ def items_all (self ) -> List [Tuple [str , List [_T ]]]:
216215 """List of (key, list of values for key)."""
217216 return [(k , self .getall (k )) for k in self ]
218217
@@ -238,7 +237,7 @@ def get_config_path(config_level: Lit_config_levels) -> str:
238237 assert_never (config_level , ValueError (f"Invalid configuration level: { config_level !r} " ))
239238
240239
241- class GitConfigParser (with_metaclass ( MetaParserBuilder , cp .RawConfigParser )): # type: ignore ## mypy does not understand dynamic class creation # noqa: E501
240+ class GitConfigParser (cp .RawConfigParser , metaclass = MetaParserBuilder ):
242241
243242 """Implements specifics required to read git style configuration files.
244243
@@ -298,7 +297,10 @@ def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Unio
298297 :param repo: Reference to repository to use if [includeIf] sections are found in configuration files.
299298
300299 """
301- cp .RawConfigParser .__init__ (self , dict_type = _OMD )
300+ cp .RawConfigParser .__init__ (self , dict_type = _OMD ) # type: ignore[arg-type]
301+ self ._dict : Callable [..., _OMD ] # type: ignore[assignment] # mypy/typeshed bug
302+ self ._defaults : _OMD # type: ignore[assignment] # mypy/typeshed bug
303+ self ._sections : _OMD # type: ignore[assignment] # mypy/typeshed bug
302304
303305 # Used in python 3, needs to stay in sync with sections for underlying implementation to work
304306 if not hasattr (self , '_proxies' ):
@@ -309,9 +311,9 @@ def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Unio
309311 else :
310312 if config_level is None :
311313 if read_only :
312- self ._file_or_files = [get_config_path (f )
314+ self ._file_or_files = [get_config_path (cast ( Lit_config_levels , f ) )
313315 for f in CONFIG_LEVELS
314- if is_config_level ( f ) and f != 'repository' ]
316+ if f != 'repository' ]
315317 else :
316318 raise ValueError ("No configuration level or configuration files specified" )
317319 else :
@@ -424,7 +426,7 @@ def string_decode(v: str) -> str:
424426 # is it a section header?
425427 mo = self .SECTCRE .match (line .strip ())
426428 if not is_multi_line and mo :
427- sectname = mo .group ('header' ).strip ()
429+ sectname : str = mo .group ('header' ).strip ()
428430 if sectname in self ._sections :
429431 cursect = self ._sections [sectname ]
430432 elif sectname == cp .DEFAULTSECT :
@@ -535,7 +537,7 @@ def _included_paths(self) -> List[Tuple[str, str]]:
535537
536538 return paths
537539
538- def read (self ) -> None :
540+ def read (self ) -> None : # type: ignore[override]
539541 """Reads the data stored in the files we have been initialized with. It will
540542 ignore files that cannot be read, possibly leaving an empty configuration
541543
@@ -623,10 +625,11 @@ def write_section(name, section_dict):
623625
624626 if self ._defaults :
625627 write_section (cp .DEFAULTSECT , self ._defaults )
628+ value : TBD
626629 for name , value in self ._sections .items ():
627630 write_section (name , value )
628631
629- def items (self , section_name : str ) -> List [Tuple [str , str ]]:
632+ def items (self , section_name : str ) -> List [Tuple [str , str ]]: # type: ignore[override]
630633 """:return: list((option, value), ...) pairs of all items in the given section"""
631634 return [(k , v ) for k , v in super (GitConfigParser , self ).items (section_name ) if k != '__name__' ]
632635
0 commit comments