Commit Diff


commit - 013c16720913591801f3d39843d1d94900de854a
commit + b829ddf046a43c54fde990d994a0106a584822d6
blob - 2eab7d9cdfb1af20c1aad078a524b0de85c70d56
blob + 4ceecab182fd7d58ab8dab0dd4d0d18371d75afc
--- dulwich/config.py
+++ dulwich/config.py
@@ -30,19 +30,17 @@ import os
 import sys
 import warnings
 
-from typing import BinaryIO, Iterator, KeysView, Optional, Tuple, Union
+from typing import (
+    BinaryIO,
+    Iterable,
+    Iterator,
+    KeysView,
+    MutableMapping,
+    Optional,
+    Tuple,
+    Union,
+)
 
-try:
-    from collections.abc import (
-        Iterable,
-        MutableMapping,
-    )
-except ImportError:  # python < 3.7
-    from collections import (  # type: ignore
-        Iterable,
-        MutableMapping,
-    )
-
 from dulwich.file import GitFile
 
 
@@ -252,35 +250,52 @@ class Config(object):
         return name in self.sections()
 
 
-class ConfigDict(Config, MutableMapping):
+BytesLike = Union[bytes, str]
+Key = Tuple[bytes, ...]
+KeyLike = Union[bytes, str, Tuple[BytesLike, ...]]
+Value = Union[bytes, bool]
+ValueLike = Union[bytes, str, bool]
+
+
+class ConfigDict(Config, MutableMapping[Key, MutableMapping[bytes, Value]]):
     """Git configuration stored in a dictionary."""
 
-    def __init__(self, values=None, encoding=None):
+    def __init__(
+        self,
+        values: Union[
+            MutableMapping[Key, MutableMapping[bytes, Value]], None
+        ] = None,
+        encoding: Union[str, None] = None
+    ) -> None:
         """Create a new ConfigDict."""
         if encoding is None:
             encoding = sys.getdefaultencoding()
         self.encoding = encoding
         self._values = CaseInsensitiveOrderedMultiDict.make(values)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return "%s(%r)" % (self.__class__.__name__, self._values)
 
-    def __eq__(self, other):
+    def __eq__(self, other: object) -> bool:
         return isinstance(other, self.__class__) and other._values == self._values
 
-    def __getitem__(self, key):
+    def __getitem__(self, key: Key) -> MutableMapping[bytes, Value]:
         return self._values.__getitem__(key)
 
-    def __setitem__(self, key, value):
+    def __setitem__(
+        self,
+        key: Key,
+        value: MutableMapping[bytes, Value]
+    ) -> None:
         return self._values.__setitem__(key, value)
 
-    def __delitem__(self, key):
+    def __delitem__(self, key: Key) -> None:
         return self._values.__delitem__(key)
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[Key]:
         return self._values.__iter__()
 
-    def __len__(self):
+    def __len__(self) -> int:
         return self._values.__len__()
 
     @classmethod
@@ -291,11 +306,15 @@ class ConfigDict(Config, MutableMapping):
         else:
             return (parts[0], None, parts[1])
 
-    def _check_section_and_name(self, section, name):
+    def _check_section_and_name(
+        self,
+        section: KeyLike,
+        name: BytesLike
+    ) -> Tuple[Key, bytes]:
         if not isinstance(section, tuple):
             section = (section,)
 
-        section = tuple(
+        checked_section = tuple(
             [
                 subsection.encode(self.encoding)
                 if not isinstance(subsection, bytes)
@@ -307,9 +326,13 @@ class ConfigDict(Config, MutableMapping):
         if not isinstance(name, bytes):
             name = name.encode(self.encoding)
 
-        return section, name
+        return checked_section, name
 
-    def get_multivar(self, section, name):
+    def get_multivar(
+        self,
+        section: KeyLike,
+        name: BytesLike
+    ) -> Iterator[Value]:
         section, name = self._check_section_and_name(section, name)
 
         if len(section) > 1:
@@ -322,9 +345,9 @@ class ConfigDict(Config, MutableMapping):
 
     def get(  # type: ignore[override]
         self,
-        section: Union[bytes, str, Tuple[Union[bytes, str], ...]],
-        name: Union[str, bytes]
-    ) -> Optional[bytes]:
+        section: KeyLike,
+        name: BytesLike,
+    ) -> Optional[Value]:
         section, name = self._check_section_and_name(section, name)
 
         if len(section) > 1:
@@ -335,18 +358,26 @@ class ConfigDict(Config, MutableMapping):
 
         return self._values[(section[0],)][name]
 
-    def set(self, section, name, value):
+    def set(
+        self,
+        section: KeyLike,
+        name: BytesLike,
+        value: ValueLike,
+    ) -> None:
         section, name = self._check_section_and_name(section, name)
 
-        if type(value) not in (bool, bytes):
+        if not isinstance(value, (bytes, bool)):
             value = value.encode(self.encoding)
 
         self._values.setdefault(section)[name] = value
 
-    def items(self, section):
+    def items(  # type: ignore[override]
+        self,
+        section: Key
+    ) -> Iterator[Value]:
         return self._values.get(section).items()
 
-    def sections(self):
+    def sections(self) -> Iterator[Key]:
         return self._values.keys()
 
 
@@ -696,7 +727,9 @@ def parse_submodules(config: ConfigFile) -> Iterator[T
         section_kind, section_name = section
         if section_kind == b"submodule":
             sm_path = config.get(section, b"path")
+            assert isinstance(sm_path, bytes)
             assert sm_path is not None
             sm_url = config.get(section, b"url")
             assert sm_url is not None
+            assert isinstance(sm_url, bytes)
             yield (sm_path, sm_url, section_name)
blob - 1c1d8c4cb574c330838b5e118156315ec1e2ec44
blob + 34e8dda184436f34544f1badd646cde24bcf7851
--- dulwich/porcelain.py
+++ dulwich/porcelain.py
@@ -1003,6 +1003,7 @@ def get_remote_repo(
         remote_name = encoded_location.decode()
         url = config.get(section, "url")
         assert url is not None
+        assert isinstance(url, bytes)
         encoded_location = url
     else:
         remote_name = None