Skip to content

Commit

Permalink
union_test.py: cover Python structural pattern matching (PEP 634)
Browse files Browse the repository at this point in the history
Reviewed By: yoney

Differential Revision: D66863416

fbshipit-source-id: e0b6b23c32f553791a664702a7d0ea632611770c
  • Loading branch information
Aristidis Papaioannou authored and facebook-github-bot committed Dec 7, 2024
1 parent f1906a0 commit 05f3cc5
Showing 1 changed file with 235 additions and 0 deletions.
235 changes: 235 additions & 0 deletions thrift/test/thrift-python/union_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,117 @@ def test_union_field_enum_type_annotations(self) -> None:
self.assertEqual(u_value, "Hello!")
self.assertEqual(u_fbthrift_current_value, "Hello!")

def test_match(self) -> None:
# Canonical case: union with a field set matches Class pattern with
# corresponding field keyword argument (but not a different keyword argument):
match TestUnionImmutable(string_field="Hello!"):
case TestUnionImmutable(int_field=int_field):
self.fail(f"Unexpected match: {int_field}")
case TestUnionImmutable(string_field=string_field):
self.assertEqual(string_field, "Hello!")
case _:
self.fail("Expected match, got none.")

# A non-empty union will match a Class pattern with no argument (the
# specification explicitly says this checks isinstance() only).
match TestUnionImmutable(string_field="Hello!"):
case TestUnionImmutable():
pass # Expected
case _:
self.fail("Expected match, got none.")

# Positional parameters in class pattern are not supported
with self.assertRaisesRegex(
TypeError, r"TestUnion\(\) accepts 0 positional sub-patterns \(1 given\)"
):
match TestUnionImmutable(string_field="Hello!"):
# pyre-ignore[16]: Intentional for test
case TestUnionImmutable(string_field):
pass # Should not be reached

# Matching with multiple (field) keywords arguments ???
match TestUnionImmutable(string_field="Hello!"):
case TestUnionImmutable(string_field=string_field, int_field=int_field):
self.fail(f"Unexpected match: {string_field}, {int_field}")
case TestUnionImmutable(string_field=string_field, int_field=None):
self.fail(f"Unexpected match: {string_field}")
case _:
pass # Expected

# Matching with special attributes
match TestUnionImmutable(string_field="Hello!"):
case TestUnionImmutable(
fbthrift_current_field=x,
fbthrift_current_value=y,
string_field=string_field,
):
self.assertEqual(
x, TestUnionImmutable.FbThriftUnionFieldEnum.string_field
)
self.assertEqual(y, "Hello!")
self.assertEqual(string_field, "Hello!")
case _:
self.fail("Expected match, got none.")

# Matching empty union (using fbthrift_current_value)
match TestUnionImmutable():
case TestUnionImmutable(fbthrift_current_value=None):
pass
case _:
self.fail("Expected match, got none.")

# Matching empty union (using fbthrift_current_field)
match TestUnionImmutable():
case TestUnionImmutable(
fbthrift_current_field=TestUnionImmutable.FbThriftUnionFieldEnum.EMPTY
):
pass
case _:
self.fail("Expected match, got none.")

# Match nested struct data
match TestUnionImmutable(
struct_field=TestStructImmutable(unqualified_string="Hello!")
):
case TestUnionImmutable(string_field=string_field):
self.fail(f"Unexpected match: {string_field}")
case TestUnionImmutable(fbthrift_current_value=None):
self.fail("Unexpected match: (union is not empty)")
case TestUnionImmutable(
struct_field=TestStructImmutable(
unqualified_string=unqualified_string,
optional_string=optional_string,
)
):
self.assertEqual(unqualified_string, "Hello!")
self.assertIsNone(optional_string)
case _:
self.fail("Expected match, got none.")

# Match fields with adapted types
match TestUnionAdaptedTypesImmutable(
adapted_i32_to_datetime=datetime.fromtimestamp(1733507988)
):
case TestUnionAdaptedTypesImmutable(adapted_i32_to_datetime=x):
self.assertIsInstance(x, datetime)
self.assertEqual(x, datetime.fromtimestamp(1733507988))
case _:
self.fail("Expected match, got none.")

# Match adapted types via fbthrift_current_value returns the underlying value!
# (see test_adapted_types() above).
match TestUnionAdaptedTypesImmutable(
adapted_i32_to_datetime=datetime.fromtimestamp(1733507988)
):
case TestUnionAdaptedTypesImmutable(
fbthrift_current_field=TestUnionAdaptedTypesImmutable.FbThriftUnionFieldEnum.adapted_i32_to_datetime,
fbthrift_current_value=x,
):
self.assertNotIsInstance(x, datetime)
self.assertEqual(x, 1733507988)
case _:
self.fail("Expected match, got none.")


class ThriftPython_MutableUnion_Test(unittest.TestCase):
def setUp(self) -> None:
Expand Down Expand Up @@ -915,3 +1026,127 @@ def test_union_field_enum_type_annotations(self) -> None:
)

self.assertEqual(u_fbthrift_current_value, "Hello!")

def test_match(self) -> None:
# Canonical case: union with a field set matches Class pattern with
# corresponding field keyword argument (but not a different keyword argument):
match TestUnionMutable(string_field="Hello!"):
case TestUnionMutable(int_field=int_field):
self.fail(f"Unexpected match: {int_field}")
case TestUnionMutable(string_field=string_field):
self.assertEqual(string_field, "Hello!")
case _:
self.fail("Expected match, got none.")

# A non-empty union will match a Class pattern with no argument (the
# specification explicitly says this checks isinstance() only).
match TestUnionMutable(string_field="Hello!"):
case TestUnionMutable():
pass # Expected
case _:
self.fail("Expected match, got none.")

# Positional parameters in class pattern are not supported
with self.assertRaisesRegex(
TypeError, r"TestUnion\(\) accepts 0 positional sub-patterns \(1 given\)"
):
match TestUnionMutable(string_field="Hello!"):
# pyre-ignore[16]: Intentional for test
case TestUnionMutable(string_field):
pass # Should not be reached

# Matching with multiple (field) keywords arguments ???
match TestUnionMutable(string_field="Hello!"):
case TestUnionMutable(string_field=string_field, int_field=int_field):
self.fail(f"Unexpected match: {string_field}, {int_field}")
case TestUnionMutable(string_field=string_field, int_field=None):
self.fail(f"Unexpected match: {string_field}")
case _:
pass # Expected

# Matching with special attributes
match TestUnionMutable(string_field="Hello!"):
case TestUnionMutable(
fbthrift_current_field=x,
fbthrift_current_value=y,
string_field=string_field,
):
self.assertEqual(
x, TestUnionMutable.FbThriftUnionFieldEnum.string_field
)
self.assertEqual(y, "Hello!")
self.assertEqual(string_field, "Hello!")
case _:
self.fail("Expected match, got none.")

# Matching empty union (using fbthrift_current_value)
match TestUnionMutable():
case TestUnionMutable(fbthrift_current_value=None):
pass
case _:
self.fail("Expected match, got none.")

# Matching empty union (using fbthrift_current_field)
match TestUnionMutable():
case TestUnionMutable(
fbthrift_current_field=TestUnionMutable.FbThriftUnionFieldEnum.EMPTY
):
pass
case _:
self.fail("Expected match, got none.")

# Match nested struct data
match TestUnionMutable(
struct_field=TestStructMutable(unqualified_string="Hello!")
):
case TestUnionMutable(string_field=string_field):
self.fail(f"Unexpected match: {string_field}")
case TestUnionMutable(fbthrift_current_value=None):
self.fail("Unexpected match: (union is not empty)")
case TestUnionMutable(
struct_field=TestStructMutable(
unqualified_string=unqualified_string,
optional_string=optional_string,
)
):
self.assertEqual(unqualified_string, "Hello!")
self.assertIsNone(optional_string)
case _:
self.fail("Expected match, got none.")

# Match fields with adapted types
match TestUnionAdaptedTypesMutable(
adapted_i32_to_datetime=datetime.fromtimestamp(1733507988)
):
case TestUnionAdaptedTypesMutable(adapted_i32_to_datetime=x):
self.assertIsInstance(x, datetime)
self.assertEqual(x, datetime.fromtimestamp(1733507988))
case _:
self.fail("Expected match, got none.")

# Match adapted types via fbthrift_current_value returns the underlying value!
# (see test_adapted_types() above).
match TestUnionAdaptedTypesMutable(
adapted_i32_to_datetime=datetime.fromtimestamp(1733507988)
):
case TestUnionAdaptedTypesMutable(
fbthrift_current_field=TestUnionAdaptedTypesMutable.FbThriftUnionFieldEnum.adapted_i32_to_datetime,
fbthrift_current_value=x,
):
self.assertNotIsInstance(x, datetime)
self.assertEqual(x, 1733507988)
case _:
self.fail("Expected match, got none.")

# Cases above are similar to immutable union. Checking with mutations next...

# Similar to canonical case above, but field is changed after initialization.
u = TestUnionMutable(string_field="Hello!")
u.int_field = 42
match u:
case TestUnionMutable(string_field=string_field):
self.fail(f"Unexpected match: {string_field}")
case TestUnionMutable(int_field=int_field):
self.assertEqual(int_field, 42)
case _:
self.fail("Expected match, got none.")

0 comments on commit 05f3cc5

Please sign in to comment.