6
6
"""Module containing module parser implementation able to properly read and write
7
7
configuration files"""
8
8
9
+ import sys
9
10
import abc
10
11
from functools import wraps
11
12
import inspect
14
15
import os
15
16
import re
16
17
import fnmatch
17
- from collections import OrderedDict
18
18
19
19
from git .compat import (
20
20
defenc ,
21
21
force_text ,
22
- with_metaclass ,
23
22
is_win ,
24
23
)
25
24
31
30
32
31
# typing-------------------------------------------------------
33
32
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 )
36
35
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
38
37
39
38
if TYPE_CHECKING :
40
39
from git .repo .base import Repo
41
40
from io import BytesIO
42
41
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
+
43
51
# -------------------------------------------------------------
44
52
45
53
__all__ = ('GitConfigParser' , 'SectionConstraint' )
61
69
62
70
63
71
class MetaParserBuilder (abc .ABCMeta ):
64
-
65
72
"""Utlity class wrapping base-class methods into decorators that assure read-only properties"""
66
73
def __new__ (cls , name : str , bases : TBD , clsdict : Dict [str , Any ]) -> TBD :
67
74
"""
@@ -115,7 +122,7 @@ def flush_changes(self, *args: Any, **kwargs: Any) -> Any:
115
122
return flush_changes
116
123
117
124
118
- class SectionConstraint (object ):
125
+ class SectionConstraint (Generic [ T_ConfigParser ] ):
119
126
120
127
"""Constrains a ConfigParser to only option commands which are constrained to
121
128
always use the section we have been initialized with.
@@ -128,7 +135,7 @@ class SectionConstraint(object):
128
135
_valid_attrs_ = ("get_value" , "set_value" , "get" , "set" , "getint" , "getfloat" , "getboolean" , "has_option" ,
129
136
"remove_section" , "remove_option" , "options" )
130
137
131
- def __init__ (self , config : 'GitConfigParser' , section : str ) -> None :
138
+ def __init__ (self , config : T_ConfigParser , section : str ) -> None :
132
139
self ._config = config
133
140
self ._section_name = section
134
141
@@ -149,26 +156,26 @@ def _call_config(self, method: str, *args: Any, **kwargs: Any) -> Any:
149
156
return getattr (self ._config , method )(self ._section_name , * args , ** kwargs )
150
157
151
158
@property
152
- def config (self ) -> 'GitConfigParser' :
159
+ def config (self ) -> T_ConfigParser :
153
160
"""return: Configparser instance we constrain"""
154
161
return self ._config
155
162
156
163
def release (self ) -> None :
157
164
"""Equivalent to GitConfigParser.release(), which is called on our underlying parser instance"""
158
165
return self ._config .release ()
159
166
160
- def __enter__ (self ) -> 'SectionConstraint' :
167
+ def __enter__ (self ) -> 'SectionConstraint[T_ConfigParser] ' :
161
168
self ._config .__enter__ ()
162
169
return self
163
170
164
171
def __exit__ (self , exception_type : str , exception_value : str , traceback : str ) -> None :
165
172
self ._config .__exit__ (exception_type , exception_value , traceback )
166
173
167
174
168
- class _OMD (OrderedDict ):
175
+ class _OMD (OrderedDict_OMD ):
169
176
"""Ordered multi-dict."""
170
177
171
- def __setitem__ (self , key : str , value : Any ) -> None :
178
+ def __setitem__ (self , key : str , value : _T ) -> None : # type: ignore[override]
172
179
super (_OMD , self ).__setitem__ (key , [value ])
173
180
174
181
def add (self , key : str , value : Any ) -> None :
@@ -177,7 +184,7 @@ def add(self, key: str, value: Any) -> None:
177
184
return None
178
185
super (_OMD , self ).__getitem__ (key ).append (value )
179
186
180
- def setall (self , key : str , values : Any ) -> None :
187
+ def setall (self , key : str , values : List [ _T ] ) -> None :
181
188
super (_OMD , self ).__setitem__ (key , values )
182
189
183
190
def __getitem__ (self , key : str ) -> Any :
@@ -194,25 +201,17 @@ def setlast(self, key: str, value: Any) -> None:
194
201
prior = super (_OMD , self ).__getitem__ (key )
195
202
prior [- 1 ] = value
196
203
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
207
206
208
- def getall (self , key : str ) -> Any :
207
+ def getall (self , key : str ) -> List [ _T ] :
209
208
return super (_OMD , self ).__getitem__ (key )
210
209
211
- def items (self ) -> List [Tuple [str , Any ]]: # type: ignore[override]
210
+ def items (self ) -> List [Tuple [str , _T ]]: # type: ignore[override]
212
211
"""List of (key, last value for key)."""
213
212
return [(k , self [k ]) for k in self ]
214
213
215
- def items_all (self ) -> List [Tuple [str , List [Any ]]]:
214
+ def items_all (self ) -> List [Tuple [str , List [_T ]]]:
216
215
"""List of (key, list of values for key)."""
217
216
return [(k , self .getall (k )) for k in self ]
218
217
@@ -238,7 +237,7 @@ def get_config_path(config_level: Lit_config_levels) -> str:
238
237
assert_never (config_level , ValueError (f"Invalid configuration level: { config_level !r} " ))
239
238
240
239
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 ):
242
241
243
242
"""Implements specifics required to read git style configuration files.
244
243
@@ -298,7 +297,10 @@ def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Unio
298
297
:param repo: Reference to repository to use if [includeIf] sections are found in configuration files.
299
298
300
299
"""
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
302
304
303
305
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
304
306
if not hasattr (self , '_proxies' ):
@@ -309,9 +311,9 @@ def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Unio
309
311
else :
310
312
if config_level is None :
311
313
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 ) )
313
315
for f in CONFIG_LEVELS
314
- if is_config_level ( f ) and f != 'repository' ]
316
+ if f != 'repository' ]
315
317
else :
316
318
raise ValueError ("No configuration level or configuration files specified" )
317
319
else :
@@ -424,7 +426,7 @@ def string_decode(v: str) -> str:
424
426
# is it a section header?
425
427
mo = self .SECTCRE .match (line .strip ())
426
428
if not is_multi_line and mo :
427
- sectname = mo .group ('header' ).strip ()
429
+ sectname : str = mo .group ('header' ).strip ()
428
430
if sectname in self ._sections :
429
431
cursect = self ._sections [sectname ]
430
432
elif sectname == cp .DEFAULTSECT :
@@ -535,7 +537,7 @@ def _included_paths(self) -> List[Tuple[str, str]]:
535
537
536
538
return paths
537
539
538
- def read (self ) -> None :
540
+ def read (self ) -> None : # type: ignore[override]
539
541
"""Reads the data stored in the files we have been initialized with. It will
540
542
ignore files that cannot be read, possibly leaving an empty configuration
541
543
@@ -623,10 +625,11 @@ def write_section(name, section_dict):
623
625
624
626
if self ._defaults :
625
627
write_section (cp .DEFAULTSECT , self ._defaults )
628
+ value : TBD
626
629
for name , value in self ._sections .items ():
627
630
write_section (name , value )
628
631
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]
630
633
""":return: list((option, value), ...) pairs of all items in the given section"""
631
634
return [(k , v ) for k , v in super (GitConfigParser , self ).items (section_name ) if k != '__name__' ]
632
635
0 commit comments