Skip to content

Commit

Permalink
Merge branch 'stable'
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism committed Nov 4, 2024
2 parents 357681f + 4fd7073 commit 9a69323
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 34 deletions.
11 changes: 11 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ Version 3.2.0
Unreleased


Version 3.1.2
-------------

Released 2024-11-04

- Improve type annotation for ``TypeConversionDict.get`` to allow the ``type``
parameter to be a callable. :issue:`2988`
- ``Headers`` does not inherit from ``MutableMapping``, as it is does not
exactly match that interface. :issue:`2989`


Version 3.1.1
-------------

Expand Down
39 changes: 22 additions & 17 deletions src/werkzeug/datastructures/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
T = t.TypeVar("T")


class Headers(cabc.MutableMapping[str, str]):
class Headers:
"""An object that stores some headers. It has a dict-like interface,
but is ordered, can store the same key multiple times, and iterating
yields ``(key, value)`` pairs instead of only keys.
Expand Down Expand Up @@ -107,18 +107,21 @@ def lowered(item: tuple[str, ...]) -> tuple[str, ...]:

__hash__ = None # type: ignore[assignment]

@t.overload # type: ignore[override]
@t.overload
def get(self, key: str) -> str | None: ...
@t.overload
def get(self, key: str, default: str) -> str: ...
@t.overload
def get(self, key: str, default: T) -> str | T: ...
@t.overload
def get(self, key: str, type: type[T]) -> T | None: ...
def get(self, key: str, type: cabc.Callable[[str], T]) -> T | None: ...
@t.overload
def get(self, key: str, default: T, type: type[T]) -> T: ...
def get(self, key: str, default: T, type: cabc.Callable[[str], T]) -> T: ...
def get( # type: ignore[misc]
self, key: str, default: str | T | None = None, type: type[T] | None = None
self,
key: str,
default: str | T | None = None,
type: cabc.Callable[[str], T] | None = None,
) -> str | T | None:
"""Return the default value if the requested data doesn't exist.
If `type` is provided and is a callable it should convert the value,
Expand Down Expand Up @@ -153,15 +156,17 @@ def get( # type: ignore[misc]
return rv

try:
return type(rv) # type: ignore[call-arg]
return type(rv)
except ValueError:
return default

@t.overload
def getlist(self, key: str) -> list[str]: ...
@t.overload
def getlist(self, key: str, type: type[T]) -> list[T]: ...
def getlist(self, key: str, type: type[T] | None = None) -> list[str] | list[T]:
def getlist(self, key: str, type: cabc.Callable[[str], T]) -> list[T]: ...
def getlist(
self, key: str, type: cabc.Callable[[str], T] | None = None
) -> list[str] | list[T]:
"""Return the list of items for a given key. If that key is not in the
:class:`Headers`, the return value will be an empty list. Just like
:meth:`get`, :meth:`getlist` accepts a `type` parameter. All items will
Expand All @@ -187,7 +192,7 @@ def getlist(self, key: str, type: type[T] | None = None) -> list[str] | list[T]:
for k, v in self:
if k.lower() == ikey:
try:
result.append(type(v)) # type: ignore[call-arg]
result.append(type(v))
except ValueError:
continue

Expand All @@ -203,17 +208,17 @@ def get_all(self, name: str) -> list[str]:
"""
return self.getlist(name)

def items(self, lower: bool = False) -> t.Iterable[tuple[str, str]]: # type: ignore[override]
def items(self, lower: bool = False) -> t.Iterable[tuple[str, str]]:
for key, value in self:
if lower:
key = key.lower()
yield key, value

def keys(self, lower: bool = False) -> t.Iterable[str]: # type: ignore[override]
def keys(self, lower: bool = False) -> t.Iterable[str]:
for key, _ in self.items(lower):
yield key

def values(self) -> t.Iterable[str]: # type: ignore[override]
def values(self) -> t.Iterable[str]:
for _, value in self.items():
yield value

Expand Down Expand Up @@ -317,7 +322,7 @@ def popitem(self) -> tuple[str, str]:
"""Removes a key or index and returns a (key, value) item."""
return self._list.pop()

def __contains__(self, key: str) -> bool: # type: ignore[override]
def __contains__(self, key: str) -> bool:
"""Check if a key is present."""
try:
self._get_key(key)
Expand All @@ -326,7 +331,7 @@ def __contains__(self, key: str) -> bool: # type: ignore[override]

return True

def __iter__(self) -> t.Iterator[tuple[str, str]]: # type: ignore[override]
def __iter__(self) -> t.Iterator[tuple[str, str]]:
"""Yield ``(key, value)`` tuples."""
return iter(self._list)

Expand Down Expand Up @@ -481,7 +486,7 @@ def __setitem__(
else:
self._list[key] = [(k, _str_header_value(v)) for k, v in value] # type: ignore[misc]

def update( # type: ignore[override]
def update(
self,
arg: (
Headers
Expand Down Expand Up @@ -557,7 +562,7 @@ def to_wsgi_list(self) -> list[tuple[str, str]]:
:return: list
"""
return list(self) # type: ignore[arg-type]
return list(self)

def copy(self) -> te.Self:
return self.__class__(self._list)
Expand Down Expand Up @@ -635,7 +640,7 @@ def _get_key(self, key: str) -> str:
def __len__(self) -> int:
return sum(1 for _ in self)

def __iter__(self) -> cabc.Iterator[tuple[str, str]]: # type: ignore[override]
def __iter__(self) -> cabc.Iterator[tuple[str, str]]:
for key, value in self.environ.items():
if key.startswith("HTTP_") and key not in {
"HTTP_CONTENT_TYPE",
Expand Down
44 changes: 28 additions & 16 deletions src/werkzeug/datastructures/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,14 @@ def get(self, key: K, default: V) -> V: ...
@t.overload
def get(self, key: K, default: T) -> V | T: ...
@t.overload
def get(self, key: str, type: type[T]) -> T | None: ...
def get(self, key: str, type: cabc.Callable[[V], T]) -> T | None: ...
@t.overload
def get(self, key: str, default: T, type: type[T]) -> T: ...
def get(self, key: str, default: T, type: cabc.Callable[[V], T]) -> T: ...
def get( # type: ignore[misc]
self, key: K, default: V | T | None = None, type: type[T] | None = None
self,
key: K,
default: V | T | None = None,
type: cabc.Callable[[V], T] | None = None,
) -> V | T | None:
"""Return the default value if the requested data doesn't exist.
If `type` is provided and is a callable it should convert the value,
Expand Down Expand Up @@ -108,7 +111,7 @@ def get( # type: ignore[misc]
return rv

try:
return type(rv) # type: ignore[call-arg]
return type(rv)
except (ValueError, TypeError):
return default

Expand Down Expand Up @@ -255,8 +258,10 @@ def add(self, key: K, value: V) -> None:
@t.overload
def getlist(self, key: K) -> list[V]: ...
@t.overload
def getlist(self, key: K, type: type[T]) -> list[T]: ...
def getlist(self, key: K, type: type[T] | None = None) -> list[V] | list[T]:
def getlist(self, key: K, type: cabc.Callable[[V], T]) -> list[T]: ...
def getlist(
self, key: K, type: cabc.Callable[[V], T] | None = None
) -> list[V] | list[T]:
"""Return the list of items for a given key. If that key is not in the
`MultiDict`, the return value will be an empty list. Just like `get`,
`getlist` accepts a `type` parameter. All items will be converted
Expand All @@ -279,7 +284,7 @@ def getlist(self, key: K, type: type[T] | None = None) -> list[V] | list[T]:
result = []
for item in rv:
try:
result.append(type(item)) # type: ignore[call-arg]
result.append(type(item))
except (ValueError, TypeError):
pass
return result
Expand Down Expand Up @@ -707,8 +712,10 @@ def add(self, key: K, value: V) -> None:
@t.overload
def getlist(self, key: K) -> list[V]: ...
@t.overload
def getlist(self, key: K, type: type[T]) -> list[T]: ...
def getlist(self, key: K, type: type[T] | None = None) -> list[V] | list[T]:
def getlist(self, key: K, type: cabc.Callable[[V], T]) -> list[T]: ...
def getlist(
self, key: K, type: cabc.Callable[[V], T] | None = None
) -> list[V] | list[T]:
rv: list[_omd_bucket[K, V]]

try:
Expand All @@ -720,7 +727,7 @@ def getlist(self, key: K, type: type[T] | None = None) -> list[V] | list[T]:
result = []
for item in rv:
try:
result.append(type(item.value)) # type: ignore[call-arg]
result.append(type(item.value))
except (ValueError, TypeError):
pass
return result
Expand Down Expand Up @@ -852,17 +859,20 @@ def get(self, key: K, default: V) -> V: ...
@t.overload
def get(self, key: K, default: T) -> V | T: ...
@t.overload
def get(self, key: str, type: type[T]) -> T | None: ...
def get(self, key: str, type: cabc.Callable[[V], T]) -> T | None: ...
@t.overload
def get(self, key: str, default: T, type: type[T]) -> T: ...
def get(self, key: str, default: T, type: cabc.Callable[[V], T]) -> T: ...
def get( # type: ignore[misc]
self, key: K, default: V | T | None = None, type: type[T] | None = None
self,
key: K,
default: V | T | None = None,
type: cabc.Callable[[V], T] | None = None,
) -> V | T | None:
for d in self.dicts:
if key in d:
if type is not None:
try:
return type(d[key]) # type: ignore[call-arg]
return type(d[key])
except (ValueError, TypeError):
continue
return d[key]
Expand All @@ -871,8 +881,10 @@ def get( # type: ignore[misc]
@t.overload
def getlist(self, key: K) -> list[V]: ...
@t.overload
def getlist(self, key: K, type: type[T]) -> list[T]: ...
def getlist(self, key: K, type: type[T] | None = None) -> list[V] | list[T]:
def getlist(self, key: K, type: cabc.Callable[[V], T]) -> list[T]: ...
def getlist(
self, key: K, type: cabc.Callable[[V], T] | None = None
) -> list[V] | list[T]:
rv = []
for d in self.dicts:
rv.extend(d.getlist(key, type)) # type: ignore[arg-type]
Expand Down
2 changes: 1 addition & 1 deletion src/werkzeug/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def lookup(self, obj: Request) -> WSGIEnvironment:
class header_property(_DictAccessorProperty[_TAccessorValue]):
"""Like `environ_property` but for headers."""

def lookup(self, obj: Request | Response) -> Headers:
def lookup(self, obj: Request | Response) -> Headers: # type: ignore[override]
return obj.headers


Expand Down

0 comments on commit 9a69323

Please sign in to comment.