Skip to content

Commit e1e2451

Browse files
committed
update the API of select fields to be a dict
1 parent 8e78430 commit e1e2451

File tree

3 files changed

+169
-261
lines changed

3 files changed

+169
-261
lines changed

tests/test_fields.py

Lines changed: 87 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -652,98 +652,136 @@ def x(self):
652652
self.call_counter["x"] += 1
653653
return 1
654654

655-
@field
655+
@field(disabled=False)
656656
def y(self):
657657
self.call_counter["y"] += 1
658658
return 2
659659

660-
@field
660+
@field(disabled=True)
661661
def z(self):
662662
self.call_counter["z"] += 1
663663
return 3
664664

665665

666666
@pytest.mark.asyncio
667-
async def test_select_fields_include() -> None:
668-
# Basic case
669-
page = BigPage(SelectFields(include=["x", "y"]))
667+
async def test_select_fields() -> None:
668+
# Required fields from the item cls which are not included raise an TypeError
669+
expected_type_error_msg = (
670+
r"__init__\(\) missing 1 required positional argument: 'x'"
671+
)
672+
673+
# When SelectFields isn't set, it should simply extract the non-disabled
674+
# fields.
675+
page = BigPage()
676+
item = await page.to_item()
677+
assert item == BigItem(x=1, y=2, z=None)
678+
assert page.fields_to_extract == ["x", "y"]
679+
assert page.call_counter == {"x": 1, "y": 1}
680+
681+
# If no field selection directive is given but SelectFields is set, it would
682+
# use the default fields that are not disabled.
683+
page = BigPage(SelectFields(fields=None))
684+
item = await page.to_item()
685+
assert item == BigItem(x=1, y=2, z=None)
686+
assert page.fields_to_extract == ["x", "y"]
687+
assert page.call_counter == {"x": 1, "y": 1}
688+
689+
# Same case as above but given an empty dict.
690+
page = BigPage(SelectFields(fields={}))
670691
item = await page.to_item()
671692
assert item == BigItem(x=1, y=2, z=None)
672-
assert page.fields_to_extract == {"x", "y"}
693+
assert page.fields_to_extract == ["x", "y"]
673694
assert page.call_counter == {"x": 1, "y": 1}
674695

675-
# Repeated fields are ignored
676-
page = BigPage(SelectFields(include=["x", "x"]))
696+
# Select all fields
697+
page = BigPage(SelectFields(fields={"*": True}))
698+
item = await page.to_item()
699+
assert item == BigItem(x=1, y=2, z=3)
700+
assert page.fields_to_extract == ["x", "y", "z"]
701+
assert page.call_counter == {"x": 1, "y": 1, "z": 1}
702+
703+
# Don't select all fields
704+
page = BigPage(SelectFields(fields={"*": False}))
705+
with pytest.raises(TypeError, match=expected_type_error_msg):
706+
await page.to_item()
707+
assert page.fields_to_extract == []
708+
assert page.call_counter == {}
709+
710+
# Exclude all but one
711+
page = BigPage(SelectFields(fields={"*": False, "x": True}))
677712
item = await page.to_item()
678713
assert item == BigItem(x=1, y=None, z=None)
679-
assert page.fields_to_extract == {"x"}
714+
assert page.fields_to_extract == ["x"]
680715
assert page.call_counter == {"x": 1}
681716

682-
# Passing None value results in all fields to be extracted.
683-
# Note that this is different when [] is passed. See test below.
684-
page = BigPage(SelectFields(include=None))
717+
# Include all fields but one
718+
page = BigPage(SelectFields(fields={"*": True, "z": False}))
719+
item = await page.to_item()
720+
assert item == BigItem(x=1, y=2, z=None)
721+
assert page.fields_to_extract == ["x", "y"]
722+
assert page.call_counter == {"x": 1, "y": 1}
723+
724+
# overlapping directives on the same field should be okay
725+
page = BigPage(SelectFields(fields={"*": True, "x": True, "y": True, "z": True}))
685726
item = await page.to_item()
686727
assert item == BigItem(x=1, y=2, z=3)
687-
assert page.fields_to_extract == {"x", "y", "z"}
728+
assert page.fields_to_extract == ["x", "y", "z"]
688729
assert page.call_counter == {"x": 1, "y": 1, "z": 1}
689730

690-
# Required fields from the item cls which are not included raise an TypeError
691-
expected_type_error_msg = (
692-
r"__init__\(\) missing 1 required positional argument: 'x'"
693-
)
694-
page = BigPage(SelectFields(include=[]))
731+
# Exluding a required field throws an error
732+
page = BigPage(SelectFields(fields={"x": False}))
695733
with pytest.raises(TypeError, match=expected_type_error_msg):
696734
item = await page.to_item()
697-
assert page.fields_to_extract == set()
698-
assert page.call_counter == {}
735+
assert page.fields_to_extract == ["y"]
736+
assert page.call_counter == {"y": 1}
699737

700-
page = BigPage(SelectFields(include=["y", "z"]))
701-
with pytest.raises(TypeError, match=expected_type_error_msg):
702-
await page.to_item()
703-
assert page.fields_to_extract == {"y", "z"}
704-
assert page.call_counter == {"y": 1, "z": 1}
738+
# boolean-like values are not supported. They are simply ignored and the
739+
# page would revert back to the default field directives.
740+
page = BigPage(SelectFields(fields={"x": 0, "y": 0, "z": 1})) # type: ignore[dict-item]
741+
item = await page.to_item()
742+
assert item == BigItem(x=1, y=2, z=None)
743+
assert page.fields_to_extract == ["x", "y"]
744+
assert page.call_counter == {"x": 1, "y": 1}
705745

706746
# The remaining tests below checks the different behaviors when encountering a
707747
# field which doesn't existing in the PO
708-
fields = ["x", "not_existing"]
748+
fields = {"x": True, "not_existing": True}
709749
expected_attribute_error_msg = (
710-
"Field 'not_existing' isn't available in tests.test_fields.BigPage"
750+
"The {'not_existing'} fields isn't available in tests.test_fields.BigPage"
711751
)
712752

713753
# Unknown field raises an AttributeError by default
714-
page = BigPage(SelectFields(include=fields))
754+
page = BigPage(SelectFields(fields=fields))
715755
with pytest.raises(AttributeError, match=expected_attribute_error_msg):
716756
await page.to_item()
717-
assert page.fields_to_extract == set(fields)
757+
with pytest.raises(AttributeError, match=expected_attribute_error_msg):
758+
assert page.fields_to_extract
718759

719-
page = BigPage(SelectFields(include=fields, on_unknown_field="raise"))
760+
page = BigPage(SelectFields(fields=fields, on_unknown_field="raise"))
720761
with pytest.raises(AttributeError, match=expected_attribute_error_msg):
721762
await page.to_item()
722-
assert page.fields_to_extract == set(fields)
763+
with pytest.raises(AttributeError, match=expected_attribute_error_msg):
764+
assert page.fields_to_extract
723765

724-
# It should safely ignore it if page object has set skip_nonitem_fields
725-
page = BigPage(SelectFields(include=fields, on_unknown_field="ignore"))
766+
# It should safely ignore it as well.
767+
page = BigPage(SelectFields(fields=fields, on_unknown_field="ignore"))
726768
with warnings.catch_warnings(record=True) as caught_warnings:
727769
item = await page.to_item()
728-
assert item == BigItem(x=1, y=None, z=None)
729770
assert not caught_warnings
730-
assert page.fields_to_extract == set(fields)
731-
assert page.call_counter == {"x": 1}
771+
assert item == BigItem(x=1, y=2, z=None)
772+
with warnings.catch_warnings(record=True) as caught_warnings:
773+
assert page.fields_to_extract == ["x", "y"]
774+
assert not caught_warnings
775+
assert page.call_counter == {"x": 1, "y": 1}
732776

733777
# When 'warn' is used, the same msg when 'raise' is used.
734-
page = BigPage(SelectFields(include=fields, on_unknown_field="warn"))
735-
with warnings.catch_warnings(record=True) as caught_warnings:
778+
page = BigPage(SelectFields(fields=fields, on_unknown_field="warn"))
779+
with pytest.warns(UserWarning, match=expected_attribute_error_msg):
736780
item = await page.to_item()
737-
assert item == BigItem(x=1, y=None, z=None)
738-
assert any(
739-
[
740-
True
741-
for w in caught_warnings
742-
if expected_attribute_error_msg in str(w.message)
743-
]
744-
)
745-
assert page.fields_to_extract == set(fields)
746-
assert page.call_counter == {"x": 1}
781+
assert item == BigItem(x=1, y=2, z=None)
782+
with pytest.warns(UserWarning, match=expected_attribute_error_msg):
783+
assert page.fields_to_extract == ["x", "y"]
784+
assert page.call_counter == {"x": 1, "y": 1}
747785

748786

749787
@pytest.mark.asyncio
@@ -759,147 +797,5 @@ async def test_select_fields_on_unknown_field_bad_value() -> None:
759797
with pytest.raises(ValueError, match=expected_value_error_msg):
760798
await BigPage(
761799
# ignore mypy error since it's expecting a valid value inside the Literal.
762-
SelectFields(include=["y", "not_existing"], on_unknown_field=invalid_val) # type: ignore[arg-type]
800+
SelectFields(fields=["y", "not_existing"], on_unknown_field=invalid_val) # type: ignore[arg-type]
763801
).to_item()
764-
765-
766-
@pytest.mark.asyncio
767-
async def test_select_fields_exclude() -> None:
768-
# Basic case
769-
page = BigPage(SelectFields(exclude=["y", "z"]))
770-
item = await page.to_item()
771-
assert item == BigItem(x=1, y=None, z=None)
772-
assert page.fields_to_extract == {"x"}
773-
assert page.call_counter == {"x": 1}
774-
775-
# Repeated fields are ignored
776-
page = BigPage(SelectFields(exclude=["y", "y"]))
777-
item = await page.to_item()
778-
assert item == BigItem(x=1, y=None, z=3)
779-
assert page.fields_to_extract == {"x", "z"}
780-
assert page.call_counter == {"x": 1, "z": 1}
781-
782-
# A value of None would return all fields
783-
page = BigPage(SelectFields(exclude=None))
784-
item = await page.to_item()
785-
assert item == BigItem(x=1, y=2, z=3)
786-
assert page.fields_to_extract == {"x", "y", "z"}
787-
assert page.call_counter == {"x": 1, "y": 1, "z": 1}
788-
789-
# Using an empty list returns all fields
790-
page = BigPage(SelectFields(exclude=[]))
791-
item = await page.to_item()
792-
assert item == BigItem(x=1, y=2, z=3)
793-
assert page.fields_to_extract == {"x", "y", "z"}
794-
assert page.call_counter == {"x": 1, "y": 1, "z": 1}
795-
796-
# Required fields from the item cls which are not included raise an TypeError
797-
expected_type_error_msg = (
798-
r"__init__\(\) missing 1 required positional argument: 'x'"
799-
)
800-
with pytest.raises(TypeError, match=expected_type_error_msg):
801-
page = BigPage(SelectFields(exclude=["x"]))
802-
await page.to_item()
803-
assert page.fields_to_extract == {"y", "z"}
804-
assert page.call_counter == {"y": 1, "z": 1}
805-
806-
# Unlike the test setup in ``test_select_fields_include()``, we don't
807-
# expect any errors here since 'exclude' actually removes them. However, if
808-
# include and exclude were used together, and include introduced an unknown
809-
# field which exclude hasn't removed, it should err out.
810-
# See ``test_select_fields_include_exclude()``.
811-
fields = ["y", "not_existing"]
812-
813-
page = BigPage(SelectFields(exclude=fields))
814-
item = await page.to_item()
815-
assert item == BigItem(x=1, y=None, z=3)
816-
assert page.fields_to_extract == {"x", "z"}
817-
assert page.call_counter == {"x": 1, "z": 1}
818-
819-
page = BigPage(SelectFields(exclude=fields, on_unknown_field="raise"))
820-
item = await page.to_item()
821-
assert item == BigItem(x=1, y=None, z=3)
822-
assert page.fields_to_extract == {"x", "z"}
823-
assert page.call_counter == {"x": 1, "z": 1}
824-
825-
page = BigPage(SelectFields(exclude=fields, on_unknown_field="ignore"))
826-
item = await page.to_item()
827-
assert item == BigItem(x=1, y=None, z=3)
828-
assert page.fields_to_extract == {"x", "z"}
829-
assert page.call_counter == {"x": 1, "z": 1}
830-
831-
with warnings.catch_warnings(record=True) as caught_warnings:
832-
page = BigPage(SelectFields(exclude=fields, on_unknown_field="warn"))
833-
item = await page.to_item()
834-
assert item == BigItem(x=1, y=None, z=3)
835-
assert not caught_warnings
836-
assert page.fields_to_extract == {"x", "z"}
837-
assert page.call_counter == {"x": 1, "z": 1}
838-
839-
840-
@pytest.mark.asyncio
841-
async def test_select_fields_include_exclude() -> None:
842-
page = BigPage(SelectFields(include=["x", "y"], exclude=["y"]))
843-
item = await page.to_item()
844-
assert item == BigItem(x=1, y=None, z=None)
845-
assert page.fields_to_extract == {"x"}
846-
assert page.call_counter == {"x": 1}
847-
848-
# If the fields cancel out, then any required field should error out.
849-
expected_type_error_msg = (
850-
r"__init__\(\) missing 1 required positional argument: 'x'"
851-
)
852-
page = BigPage(SelectFields(include=["x", "y"], exclude=["x", "y"]))
853-
with pytest.raises(TypeError, match=expected_type_error_msg):
854-
item = await page.to_item()
855-
assert page.fields_to_extract == set()
856-
assert page.call_counter == {}
857-
858-
page = BigPage(
859-
SelectFields(include=["x", "not_existing"], exclude=["not_existing"])
860-
)
861-
item = await page.to_item()
862-
assert item == BigItem(x=1, y=None, z=None)
863-
assert page.fields_to_extract == {"x"}
864-
assert page.call_counter == {"x": 1}
865-
866-
page = BigPage(SelectFields(include=["x", "y", "not_existing"], exclude=["y"]))
867-
expected_attribute_error_msg = (
868-
"Field 'not_existing' isn't available in tests.test_fields.BigPage"
869-
)
870-
with pytest.raises(AttributeError, match=expected_attribute_error_msg):
871-
await page.to_item()
872-
assert page.fields_to_extract == {"x", "not_existing"}
873-
874-
page = BigPage(SelectFields(include=None, exclude=["y"]))
875-
item = await page.to_item()
876-
assert item == BigItem(x=1, y=None, z=3)
877-
assert page.fields_to_extract == {"x", "z"}
878-
assert page.call_counter == {"x": 1, "z": 1}
879-
880-
page = BigPage(SelectFields(include=None, exclude=None))
881-
item = await page.to_item()
882-
assert item == BigItem(x=1, y=2, z=3)
883-
assert page.fields_to_extract == {"x", "y", "z"}
884-
assert page.call_counter == {"x": 1, "y": 1, "z": 1}
885-
886-
page = BigPage(SelectFields(include=None, exclude=[]))
887-
item = await page.to_item()
888-
assert item == BigItem(x=1, y=2, z=3)
889-
assert page.fields_to_extract == {"x", "y", "z"}
890-
assert page.call_counter == {"x": 1, "y": 1, "z": 1}
891-
892-
for exclude in (["y"], [], None):
893-
# Ignore some of the types below since mypy is not expecting an empty list
894-
895-
page = BigPage(SelectFields(include=[], exclude=exclude)) # type: ignore[arg-type]
896-
with pytest.raises(TypeError, match=expected_type_error_msg):
897-
item = await page.to_item()
898-
assert page.fields_to_extract == set()
899-
assert page.call_counter == {}
900-
901-
page = BigPage(SelectFields(include=["x", "z"], exclude=exclude)) # type: ignore[arg-type]
902-
item = await page.to_item()
903-
assert item == BigItem(x=1, y=None, z=3)
904-
assert page.fields_to_extract == {"x", "z"}
905-
assert page.call_counter == {"x": 1, "z": 1}

0 commit comments

Comments
 (0)