Skip to content

Commit

Permalink
pop raises type conversion failure
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism committed Oct 27, 2024
1 parent 774bef2 commit f407e3c
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 39 deletions.
55 changes: 28 additions & 27 deletions src/werkzeug/datastructures/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ def __repr__(self):


class TypeConversionDict(dict):
"""Works like a regular dict but the :meth:`get` method can perform
type conversions. :class:`MultiDict` and :class:`CombinedMultiDict`
"""Works like a regular dict but the :meth:`get` and :meth:`pop` methods can
perform type conversions. :class:`MultiDict` and :class:`CombinedMultiDict`
are subclasses of this class and provide the same feature.
.. versionadded:: 0.5
Expand Down Expand Up @@ -89,51 +89,52 @@ def get(self, key, default=None, type=None):
return rv

def pop(self, key, default=_missing, type=None):
"""Like :meth:`get` but removes the key/value pair.
"""Similar to :meth:`get` but removes the item, and does not default to
``None`` if the key doesn't exist. If type conversion fails and no
default is given, the item is not removed and the error is re-raised.
>>> d = TypeConversionDict(foo='42', bar='blub')
>>> d.pop('foo', type=int)
>>> d = TypeConversionDict(a="42", b="abc", c="def")
>>> d.pop("a", type=int)
42
>>> 'foo' in d
>>> "a" in d
False
>>> d.pop('bar', -1, type=int)
>>> d.pop("b", -1, type=int)
-1
>>> 'bar' in d
>>> "b" in d
False
>>> d.pop("c", type=int)
ValueError: ...
>>> "c" in d
True
:param key: The key to be looked up.
:param default: The default value to be returned if the key is not
in the dictionary. If not further specified it's
an :exc:`KeyError`.
:param type: A callable that is used to cast the value in the dict.
If a :exc:`ValueError` or a :exc:`TypeError` is raised
by this callable the default value is returned.
.. admonition:: note
If the type conversion fails, the key is **not** removed from the
dictionary.
:param default: The value to be returned if the key doesn't exist. If
not given, a ``KeyError`` is raised.
:param type: A callable that is used to convert the value.
If a ``ValueError`` or ``TypeError`` is raised, the default value is
returned if given, otherwise the error is raised.
"""

try:
# Don't remove the item yet, type conversion might fail.
rv = self[key]
except KeyError:
if default is _missing:
raise

return default

if type is not None:
try:
rv = type(rv)
except (ValueError, TypeError):
if default is _missing:
return None
raise

return default
try:
# This method is not meant to be thread-safe, but at least lets not
# fall over if the dict was mutated between the get and the delete. -MK
del self[key]
except KeyError:
pass

# Remove the item after type conversion succeeds.
del self[key]
return rv


Expand Down
29 changes: 17 additions & 12 deletions tests/test_datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,19 +585,24 @@ def test_pop_value_conversion(self):
assert d.pop("foo", type=int) == 1
assert "foo" not in d

def test_pop_return_when_conversion_is_not_possible(self):
d = self.storage_class(foo="bar", baz=None)
assert d.pop("foo", type=int) is None
assert "foo" in d # key is still in the dict, because the conversion failed
assert d.pop("baz", type=int) is None
assert "baz" in d # key is still in the dict, because the conversion failed
@pytest.mark.parametrize(
("value", "exc_type"), [("a", ValueError), (None, TypeError)]
)
def test_pop_conversion_fails(self, value, exc_type):
d = self.storage_class(a=value)

def test_pop_return_default_when_conversion_is_not_possible(self):
d = self.storage_class(foo="bar", baz=None)
assert d.pop("foo", default=-1, type=int) == -1
assert "foo" in d # key is still in the dict, because the conversion failed
assert d.pop("baz", default=-1, type=int) == -1
assert "baz" in d # key is still in the dict, because the conversion failed
with pytest.raises(exc_type):
d.pop("a", type=int)

assert "a" in d

@pytest.mark.parametrize(
("value", "exc_type"), [("a", ValueError), (None, TypeError)]
)
def test_pop_conversion_fails_default(self, value, exc_type):
d = self.storage_class(a=value)
assert d.pop("a", default=None, type=int) is None
assert "a" in d

def test_pop_propagate_exceptions_in_conversion(self):
d = self.storage_class(foo="bar")
Expand Down

0 comments on commit f407e3c

Please sign in to comment.