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 (
2020defenc ,
2121force_text ,
22- with_metaclass ,
2322is_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 :
4039from git .repo .base import Repo
4140from 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"""
6673def __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:
115122return 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 :
132139self ._config = config
133140self ._section_name = section
134141
@@ -149,26 +156,26 @@ def _call_config(self, method: str, *args: Any, **kwargs: Any) -> Any:
149156return 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"""
154161return self ._config
155162
156163def release (self )-> None :
157164"""Equivalent to GitConfigParser.release(), which is called on our underlying parser instance"""
158165return self ._config .release ()
159166
160- def __enter__ (self )-> 'SectionConstraint' :
167+ def __enter__ (self )-> 'SectionConstraint[T_ConfigParser] ' :
161168self ._config .__enter__ ()
162169return self
163170
164171def __exit__ (self ,exception_type :str ,exception_value :str ,traceback :str )-> None :
165172self ._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]
172179super (_OMD ,self ).__setitem__ (key , [value ])
173180
174181def add (self ,key :str ,value :Any )-> None :
@@ -177,7 +184,7 @@ def add(self, key: str, value: Any) -> None:
177184return None
178185super (_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 :
181188super (_OMD ,self ).__setitem__ (key ,values )
182189
183190def __getitem__ (self ,key :str )-> Any :
@@ -194,25 +201,17 @@ def setlast(self, key: str, value: Any) -> None:
194201prior = super (_OMD ,self ).__getitem__ (key )
195202prior [- 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 ] :
209208return 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)."""
213212return [(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)."""
217216return [(k ,self .getall (k ))for k in self ]
218217
@@ -238,7 +237,7 @@ def get_config_path(config_level: Lit_config_levels) -> str:
238237assert_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
304306if not hasattr (self ,'_proxies' ):
@@ -309,9 +311,9 @@ def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Unio
309311else :
310312if config_level is None :
311313if read_only :
312- self ._file_or_files = [get_config_path (f )
314+ self ._file_or_files = [get_config_path (cast ( Lit_config_levels , f ) )
313315for f in CONFIG_LEVELS
314- if is_config_level ( f ) and f != 'repository' ]
316+ if f != 'repository' ]
315317else :
316318raise ValueError ("No configuration level or configuration files specified" )
317319else :
@@ -424,7 +426,7 @@ def string_decode(v: str) -> str:
424426# is it a section header?
425427mo = self .SECTCRE .match (line .strip ())
426428if not is_multi_line and mo :
427- sectname = mo .group ('header' ).strip ()
429+ sectname : str = mo .group ('header' ).strip ()
428430if sectname in self ._sections :
429431cursect = self ._sections [sectname ]
430432elif sectname == cp .DEFAULTSECT :
@@ -535,7 +537,7 @@ def _included_paths(self) -> List[Tuple[str, str]]:
535537
536538return 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
624626if self ._defaults :
625627write_section (cp .DEFAULTSECT ,self ._defaults )
628+ value :TBD
626629for name ,value in self ._sections .items ():
627630write_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"""
631634return [(k ,v )for k ,v in super (GitConfigParser ,self ).items (section_name )if k != '__name__' ]
632635