From 71e3d0bbcf899318fa8ad2db0ca406bf7317c59a Mon Sep 17 00:00:00 2001 From: Isaac To Date: Thu, 19 Mar 2026 09:39:02 -0700 Subject: [PATCH 1/2] test: add test for translating nested union types --- tests/test_gen_linkml.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_gen_linkml.py b/tests/test_gen_linkml.py index 308f1200..c19c3524 100644 --- a/tests/test_gen_linkml.py +++ b/tests/test_gen_linkml.py @@ -941,6 +941,22 @@ class Foo3(BaseModel): AnonymousSlotExpression(range="Bar2"), ] + # === Nested unions === + class Foo4(BaseModel): + x: Union[int, Union[str, Bar1]] + + slot = translate_field_to_slot(Foo4, "x") + assert slot == SlotDefinition( + name="x", + range=ANY_CLASS_DEF.name, + required=True, + any_of=[ + AnonymousSlotExpression(range="integer"), + AnonymousSlotExpression(range="string"), + AnonymousSlotExpression(range="Bar1"), + ], + ) + def test_tagged_union_schema(self): class Cat(BaseModel): pet_type: Literal["cat"] From c3de631d8985971ca59b64a53acd88f959abf760 Mon Sep 17 00:00:00 2001 From: Isaac To Date: Thu, 19 Mar 2026 10:14:11 -0700 Subject: [PATCH 2/2] test: assert full SlotDefinition equality in test_union_schema sub-cases Co-Authored-By: Claude Sonnet 4.6 --- src/pydantic2linkml/gen_linkml.py | 2 - tests/test_gen_linkml.py | 61 ++++++++++++++++++------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/pydantic2linkml/gen_linkml.py b/src/pydantic2linkml/gen_linkml.py index 63557aae..8cde23f6 100644 --- a/src/pydantic2linkml/gen_linkml.py +++ b/src/pydantic2linkml/gen_linkml.py @@ -505,8 +505,6 @@ def _get_ase(self, subschema: core_schema.CoreSchema) -> AnonymousSlotExpression } return AnonymousSlotExpression(**ase_kwargs) - - if pydantic_version >= version.parse("2.10"): def _invalid_schema(self, schema: core_schema.InvalidSchema) -> None: diff --git a/tests/test_gen_linkml.py b/tests/test_gen_linkml.py index c19c3524..1f8dc79d 100644 --- a/tests/test_gen_linkml.py +++ b/tests/test_gen_linkml.py @@ -895,7 +895,12 @@ class Foo0(BaseModel): ] slot = SlotGenerator(field_schema).generate() - assert slot.range is None + # `notes=slot.notes` is used instead of `notes=ANY` because + # `SlotDefinition.__eq__` serializes fields before comparing, which + # causes `unittest.mock.ANY` to be treated as the literal string + # `''` rather than a wildcard. The note content is checked + # separately below. + assert slot == SlotDefinition(name="x", required=True, notes=slot.notes) assert in_exactly_one_string( "The union core schema contains a tuple as a choice. " "Tuples as choices are yet to be supported.", @@ -906,40 +911,46 @@ class Foo0(BaseModel): class Foo1(BaseModel): x: Union[int, Bar1, str] - slot = translate_field_to_slot(Foo1, "x") - - assert slot.range == ANY_CLASS_DEF.name - assert slot.any_of == [ - AnonymousSlotExpression(range="integer"), - AnonymousSlotExpression(range="Bar1"), - AnonymousSlotExpression(range="string"), - ] + assert translate_field_to_slot(Foo1, "x") == SlotDefinition( + name="x", + range=ANY_CLASS_DEF.name, + required=True, + any_of=[ + AnonymousSlotExpression(range="integer"), + AnonymousSlotExpression(range="Bar1"), + AnonymousSlotExpression(range="string"), + ], + ) # === Unions of two models === class Foo2(BaseModel): x: Union[Bar1, Bar2] - slot = translate_field_to_slot(Foo2, "x") - - assert slot.range == ANY_CLASS_DEF.name - assert slot.any_of == [ - AnonymousSlotExpression(range="Bar1"), - AnonymousSlotExpression(range="Bar2"), - ] + assert translate_field_to_slot(Foo2, "x") == SlotDefinition( + name="x", + range=ANY_CLASS_DEF.name, + required=True, + any_of=[ + AnonymousSlotExpression(range="Bar1"), + AnonymousSlotExpression(range="Bar2"), + ], + ) # === Union of base types, lists, and models === class Foo3(BaseModel): x: Union[int, list[Bar1], list[str], Bar2] - slot = translate_field_to_slot(Foo3, "x") - - assert slot.range == ANY_CLASS_DEF.name - assert slot.any_of == [ - AnonymousSlotExpression(range="integer"), - AnonymousSlotExpression(range="Bar1", multivalued=True), - AnonymousSlotExpression(range="string", multivalued=True), - AnonymousSlotExpression(range="Bar2"), - ] + assert translate_field_to_slot(Foo3, "x") == SlotDefinition( + name="x", + range=ANY_CLASS_DEF.name, + required=True, + any_of=[ + AnonymousSlotExpression(range="integer"), + AnonymousSlotExpression(range="Bar1", multivalued=True), + AnonymousSlotExpression(range="string", multivalued=True), + AnonymousSlotExpression(range="Bar2"), + ], + ) # === Nested unions === class Foo4(BaseModel):