Skip to content

Commit 1d72435

Browse files
authored
MultiDictProxy doesn't rely on '_impl' anymore (#1150)
The first part of #1149
1 parent 52f7411 commit 1d72435

File tree

3 files changed

+198
-23
lines changed

3 files changed

+198
-23
lines changed

CHANGES/1150.misc.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:class:`multidict.MultiDictProxy` was refactored to rely only on
2+
:class:`multidict.MultiDict` public interface and don't touch any implementation
3+
details.

multidict/_multidict_py.py

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ def values(self) -> _ValuesView[_V]:
558558
def __eq__(self, other: object) -> bool:
559559
if not isinstance(other, Mapping):
560560
return NotImplemented
561+
if isinstance(other, MultiDictProxy):
562+
return self == other._md
561563
if isinstance(other, _Base):
562564
lft = self._impl._items
563565
rht = other._impl._items
@@ -633,7 +635,9 @@ def _extend(
633635
method: Callable[[list[tuple[str, str, _V]]], None],
634636
) -> None:
635637
if arg:
636-
if isinstance(arg, (MultiDict, MultiDictProxy)):
638+
if isinstance(arg, MultiDictProxy):
639+
arg = arg._md
640+
if isinstance(arg, MultiDict):
637641
if self._ci is not arg._ci:
638642
items = [(self._title(k), k, v) for _, k, v in arg._impl._items]
639643
else:
@@ -845,21 +849,98 @@ class CIMultiDict(_CIMixin, MultiDict[_V]):
845849
"""Dictionary with the support for duplicate case-insensitive keys."""
846850

847851

848-
class MultiDictProxy(_CSMixin, _Base[_V]):
852+
class MultiDictProxy(_CSMixin, MultiMapping[_V]):
849853
"""Read-only proxy for MultiDict instance."""
854+
_md: MultiDict[_V]
850855

851856
def __init__(self, arg: Union[MultiDict[_V], "MultiDictProxy[_V]"]):
852857
if not isinstance(arg, (MultiDict, MultiDictProxy)):
853858
raise TypeError(
854859
"ctor requires MultiDict or MultiDictProxy instance"
855860
f", not {type(arg)}"
856861
)
857-
858-
self._impl = arg._impl
862+
if (isinstance(arg, MultiDictProxy)):
863+
self._md = arg._md
864+
else:
865+
self._md = arg
859866

860867
def __reduce__(self) -> NoReturn:
861868
raise TypeError(f"can't pickle {self.__class__.__name__} objects")
862869

870+
@overload
871+
def getall(self, key: str) -> list[_V]: ...
872+
@overload
873+
def getall(self, key: str, default: _T) -> Union[list[_V], _T]: ...
874+
def getall(
875+
self, key: str, default: Union[_T, _SENTINEL] = sentinel
876+
) -> Union[list[_V], _T]:
877+
"""Return a list of all values matching the key."""
878+
if default is not sentinel:
879+
return self._md.getall(key, default)
880+
else:
881+
return self._md.getall(key)
882+
883+
@overload
884+
def getone(self, key: str) -> _V: ...
885+
@overload
886+
def getone(self, key: str, default: _T) -> Union[_V, _T]: ...
887+
def getone(
888+
self, key: str, default: Union[_T, _SENTINEL] = sentinel
889+
) -> Union[_V, _T]:
890+
"""Get first value matching the key.
891+
892+
Raises KeyError if the key is not found and no default is provided.
893+
"""
894+
if default is not sentinel:
895+
return self._md.getone(key, default)
896+
else:
897+
return self._md.getone(key)
898+
899+
# Mapping interface #
900+
901+
def __getitem__(self, key: str) -> _V:
902+
return self.getone(key)
903+
904+
@overload
905+
def get(self, key: str, /) -> Union[_V, None]: ...
906+
@overload
907+
def get(self, key: str, /, default: _T) -> Union[_V, _T]: ...
908+
def get(self, key: str, default: Union[_T, None] = None) -> Union[_V, _T, None]:
909+
"""Get first value matching the key.
910+
911+
If the key is not found, returns the default (or None if no default is provided)
912+
"""
913+
return self._md.getone(key, default)
914+
915+
def __iter__(self) -> Iterator[str]:
916+
return iter(self._md.keys())
917+
918+
def __len__(self) -> int:
919+
return len(self._md)
920+
921+
def keys(self) -> KeysView[str]:
922+
"""Return a new view of the dictionary's keys."""
923+
return self._md.keys()
924+
925+
def items(self) -> ItemsView[str, _V]:
926+
"""Return a new view of the dictionary's items *(key, value) pairs)."""
927+
return self._md.items()
928+
929+
def values(self) -> _ValuesView[_V]:
930+
"""Return a new view of the dictionary's values."""
931+
return self._md.values()
932+
933+
def __eq__(self, other: object) -> bool:
934+
return self._md == other
935+
936+
def __contains__(self, key: object) -> bool:
937+
return key in self._md
938+
939+
@reprlib.recursive_repr()
940+
def __repr__(self) -> str:
941+
body = ", ".join(f"'{k}': {v!r}" for k, v in self.items())
942+
return f"<{self.__class__.__name__}({body})>"
943+
863944
def copy(self) -> MultiDict[_V]:
864945
"""Return a copy of itself."""
865946
return MultiDict(self.items())
@@ -875,14 +956,16 @@ def __init__(self, arg: Union[MultiDict[_V], MultiDictProxy[_V]]):
875956
f", not {type(arg)}"
876957
)
877958

878-
self._impl = arg._impl
959+
super().__init__(arg)
879960

880961
def copy(self) -> CIMultiDict[_V]:
881962
"""Return a copy of itself."""
882963
return CIMultiDict(self.items())
883964

884965

885966
def getversion(md: Union[MultiDict[object], MultiDictProxy[object]]) -> int:
886-
if not isinstance(md, _Base):
967+
if isinstance(md, MultiDictProxy):
968+
md = md._md
969+
elif not isinstance(md, MultiDict):
887970
raise TypeError("Parameter should be multidict or proxy")
888971
return md._impl._version

0 commit comments

Comments
 (0)