Skip to content

Commit c7f6f4a

Browse files
committed
handle invalid SelectField usage
1 parent fb7b7bd commit c7f6f4a

File tree

2 files changed

+92
-16
lines changed

2 files changed

+92
-16
lines changed

tests/test_fields.py

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -742,16 +742,39 @@ async def test_select_fields() -> None:
742742
await item_from_select_fields(page)
743743
assert page.call_counter == {"y": 2}
744744

745-
# Boolean-like values are not supported. They are simply ignored and the
746-
# page would revert back to the default field directives.
745+
# Boolean-like values are not supported.
746+
expected_non_boolean_value_error_msg = (
747+
"SelectField only allows boolean values as keys. "
748+
"Got: {'x': 0, 'y': 0, 'z': 1}"
749+
)
747750
page = BigPage(
748751
response,
749752
select_fields=SelectFields({"x": 0, "y": 0, "z": 1}), # type: ignore[dict-item]
750753
)
751-
assert page.fields_to_extract == ["x", "y"]
752-
assert await page.to_item() == BigItem(x=1, y=2, z=None)
753-
assert await item_from_select_fields(page) == BigItem(x=1, y=2, z=None)
754-
assert page.call_counter == {"x": 2, "y": 2}
754+
with pytest.raises(ValueError, match=expected_non_boolean_value_error_msg):
755+
page.fields_to_extract
756+
with pytest.raises(ValueError, match=expected_non_boolean_value_error_msg):
757+
await page.to_item()
758+
with pytest.raises(ValueError, match=expected_non_boolean_value_error_msg):
759+
await item_from_select_fields(page)
760+
assert page.call_counter == {}
761+
762+
# If an invalid SelectFields value was passed to `select_fields` parameter
763+
expected_invalid_instance_value_error_msg = (
764+
r"The select_fields.fields parameter is expecting a Mapping. "
765+
r'Got SelectFields\(fields="not the instance it\'s expecting"\).'
766+
)
767+
page = BigPage(
768+
response,
769+
select_fields="not the instance it's expecting", # type: ignore[arg-type]
770+
)
771+
with pytest.raises(ValueError, match=expected_invalid_instance_value_error_msg):
772+
page.fields_to_extract
773+
with pytest.raises(ValueError, match=expected_invalid_instance_value_error_msg):
774+
await page.to_item()
775+
with pytest.raises(ValueError, match=expected_invalid_instance_value_error_msg):
776+
await item_from_select_fields(page)
777+
assert page.call_counter == {}
755778

756779
# If the item class doesn't have a field, it would error out.
757780
fields = {"x": True, "not_existing": True}
@@ -857,15 +880,35 @@ async def test_select_fields_but_to_item_only() -> None:
857880
with pytest.raises(TypeError, match=expected_type_error_msg):
858881
await item_from_select_fields(page)
859882

860-
# Boolean-like values are not supported. They are simply ignored and the
861-
# page would revert back to the default field directives.
883+
# Boolean-like values are not supported.
884+
expected_non_boolean_value_error_msg = (
885+
"SelectField only allows boolean values as keys. "
886+
"Got: {'x': 0, 'y': 0, 'z': 1}"
887+
)
862888
page = BigToItemOnlyPage(
863889
response,
864890
select_fields=SelectFields({"x": 0, "y": 0, "z": 1}), # type: ignore[dict-item]
865891
)
866-
assert page.fields_to_extract == []
892+
with pytest.raises(ValueError, match=expected_non_boolean_value_error_msg):
893+
page.fields_to_extract
867894
assert await page.to_item() == BigItem(x=1, y=2, z=None)
868-
assert await item_from_select_fields(page) == BigItem(x=1, y=2, z=None)
895+
with pytest.raises(ValueError, match=expected_non_boolean_value_error_msg):
896+
await item_from_select_fields(page)
897+
898+
# If an invalid SelectFields value was passed to `select_fields` parameter
899+
expected_invalid_instance_value_error_msg = (
900+
r"The select_fields.fields parameter is expecting a Mapping. "
901+
r'Got SelectFields\(fields="not the instance it\'s expecting"\).'
902+
)
903+
page = BigToItemOnlyPage(
904+
response,
905+
select_fields="not the instance it's expecting", # type: ignore[arg-type]
906+
)
907+
with pytest.raises(ValueError, match=expected_invalid_instance_value_error_msg):
908+
page.fields_to_extract
909+
assert await page.to_item() == BigItem(x=1, y=2, z=None)
910+
with pytest.raises(ValueError, match=expected_invalid_instance_value_error_msg):
911+
await item_from_select_fields(page)
869912

870913
# If the item class doesn't have a field, it would error out.
871914
fields = {"x": True, "not_existing": True}
@@ -999,17 +1042,37 @@ async def test_select_fields_but_unreliable() -> None:
9991042
await item_from_select_fields(page)
10001043
assert page.call_counter == {"x": 2, "z": 2}
10011044

1002-
# Boolean-like values are not supported. They are simply ignored and the
1003-
# page would revert back to the default field directives.
1045+
# Boolean-like values are not supported.
1046+
expected_non_boolean_value_error_msg = (
1047+
"SelectField only allows boolean values as keys. "
1048+
"Got: {'x': 0, 'y': 0, 'z': 1}"
1049+
)
10041050
page = BigUnreliablePage(
10051051
response,
10061052
select_fields=SelectFields({"x": 0, "y": 0, "z": 1}), # type: ignore[dict-item]
10071053
)
1008-
assert page.fields_to_extract == ["x"]
1054+
with pytest.raises(ValueError, match=expected_non_boolean_value_error_msg):
1055+
page.fields_to_extract
1056+
assert await page.to_item() == BigItem(x=1, y=2, z=3)
1057+
with pytest.raises(ValueError, match=expected_non_boolean_value_error_msg):
1058+
await item_from_select_fields(page)
1059+
assert page.call_counter == {"x": 1, "z": 1}
1060+
1061+
# If an invalid SelectFields value was passed to `select_fields` parameter
1062+
expected_invalid_instance_value_error_msg = (
1063+
r"The select_fields.fields parameter is expecting a Mapping. "
1064+
r'Got SelectFields\(fields="not the instance it\'s expecting"\).'
1065+
)
1066+
page = BigUnreliablePage(
1067+
response,
1068+
select_fields="not the instance it's expecting", # type: ignore[arg-type]
1069+
)
1070+
with pytest.raises(ValueError, match=expected_invalid_instance_value_error_msg):
1071+
page.fields_to_extract
10091072
assert await page.to_item() == BigItem(x=1, y=2, z=3)
1073+
with pytest.raises(ValueError, match=expected_invalid_instance_value_error_msg):
1074+
await item_from_select_fields(page)
10101075
assert page.call_counter == {"x": 1, "z": 1}
1011-
assert await item_from_select_fields(page) == BigItem(x=1, y=None, z=None)
1012-
assert page.call_counter == {"x": 2, "z": 1}
10131076

10141077
# If the item class doesn't have a field, it would error out.
10151078
fields = {"x": True, "not_existing": True}

web_poet/pages.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import abc
22
import inspect
3-
from typing import Any, ClassVar, Generic, List, Optional, Type, TypeVar
3+
from typing import Any, ClassVar, Generic, List, Mapping, Optional, Type, TypeVar
44

55
import attrs
66

@@ -91,6 +91,11 @@ def fields_to_extract(self) -> List[str]:
9191

9292
if fields is None or len(fields) == 0:
9393
return list(get_fields_dict(self))
94+
elif not isinstance(fields, Mapping):
95+
raise ValueError(
96+
f"The select_fields.fields parameter is expecting a Mapping. "
97+
f"Got {self.select_fields}."
98+
)
9499

95100
page_obj_fields = get_fields_dict(self, include_disabled=True)
96101

@@ -106,6 +111,14 @@ def fields_to_extract(self) -> List[str]:
106111
f"which has {self.select_fields}."
107112
)
108113

114+
# Ignore the type since even though 'fields' has been checked above that
115+
# it's not None, mypy loses track of that information at this point.
116+
if any([not isinstance(v, bool) for v in self.select_fields.fields.values()]): # type: ignore[union-attr]
117+
raise ValueError(
118+
f"SelectField only allows boolean values as keys. "
119+
f"Got: {self.select_fields.fields}"
120+
)
121+
109122
fields_to_extract = []
110123
for name, field_info in page_obj_fields.items():
111124
if fields.get("*") is False and fields.get(name) is not True:

0 commit comments

Comments
 (0)