Skip to content

Commit 86f7a4c

Browse files
authored
Merge pull request #341 from candleindark/add-enum-improv
BF: Bug fixes and other improvement in `SchemaBuilder.add_enum()`
2 parents ff6ff6f + 023d740 commit 86f7a4c

File tree

2 files changed

+157
-12
lines changed

2 files changed

+157
-12
lines changed

linkml_runtime/utils/schema_builder.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,25 +166,50 @@ def add_enum(
166166
"""
167167
Adds an enum to the schema
168168
169-
:param enum_def:
170-
:param permissible_values:
171-
:param replace_if_present:
172-
:param kwargs:
169+
:param enum_def: The base specification of the enum to be added
170+
:param permissible_values: Additional, or overriding, permissible values
171+
of the enum to be added
172+
:param replace_if_present: Whether to replace the enum if it already exists in
173+
the schema by name
174+
:param kwargs: Additional `EnumDefinition` properties to be set as part of the
175+
enum to be added
173176
:return: builder
174177
:raises ValueError: if enum already exists and replace_if_present=False
175178
"""
176-
if not isinstance(enum_def, EnumDefinition):
179+
if permissible_values is None:
180+
permissible_values = []
181+
182+
if isinstance(enum_def, str):
177183
enum_def = EnumDefinition(enum_def, **kwargs)
178-
if isinstance(enum_def, dict):
184+
elif isinstance(enum_def, dict):
179185
enum_def = EnumDefinition(**{**enum_def, **kwargs})
186+
else:
187+
# Ensure that `enum_def` is a `EnumDefinition` object
188+
if not isinstance(enum_def, EnumDefinition):
189+
msg = (
190+
f"enum_def must be a `str`, `dict`, or `EnumDefinition`, "
191+
f"not {type(enum_def)!r}"
192+
)
193+
raise TypeError(msg)
194+
180195
if enum_def.name in self.schema.enums and not replace_if_present:
181196
raise ValueError(f"Enum {enum_def.name} already exists")
197+
198+
# Attach the enum definition to the schema
182199
self.schema.enums[enum_def.name] = enum_def
183-
if permissible_values is not None:
184-
for pv in permissible_values:
185-
if isinstance(pv, str):
186-
pv = PermissibleValue(text=pv)
187-
enum_def.permissible_values[pv.text] = pv
200+
201+
for pv in permissible_values:
202+
if isinstance(pv, str):
203+
pv = PermissibleValue(text=pv)
204+
elif not isinstance(pv, PermissibleValue):
205+
msg = (
206+
f"A permissible value must be a `str` or "
207+
f"a `PermissibleValue` object, not {type(pv)}"
208+
)
209+
raise TypeError(msg)
210+
211+
enum_def.permissible_values[pv.text] = pv
212+
188213
return self
189214

190215
def add_prefix(self, prefix: str, url: str, replace_if_present = False) -> "SchemaBuilder":

tests/test_utils/test_schema_builder.py

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44
import pytest
55

66
from linkml_runtime.utils.schema_builder import SchemaBuilder
7-
from linkml_runtime.linkml_model import ClassDefinition, SlotDefinition
7+
from linkml_runtime.linkml_model import (
8+
ClassDefinition,
9+
SlotDefinition,
10+
EnumDefinition,
11+
PermissibleValue,
12+
)
813

914

15+
# === Tests for `SchemaBuilder.add_class` ===
1016
@pytest.mark.parametrize("replace_if_present", [True, False])
1117
def test_add_existing_class(replace_if_present):
1218
"""
@@ -144,3 +150,117 @@ def test_add_class_with_extra_kwargs(
144150
added_class = builder.schema.classes[class_name]
145151

146152
assert added_class == expected_added_class
153+
154+
155+
# === Tests for `SchemaBuilder.add_class` end ===
156+
157+
158+
# === Tests for `SchemaBuilder.add_enum` ===
159+
@pytest.mark.parametrize(
160+
("enum_def", "permissible_values", "expected_added_enum"),
161+
[
162+
(EnumDefinition(name="Color"), [], EnumDefinition(name="Color")),
163+
# invalid permissible values
164+
(EnumDefinition(name="Color"), ["RED", 3], EnumDefinition(name="Color")),
165+
(
166+
EnumDefinition(name="Color"),
167+
["RED", "BLUE"],
168+
EnumDefinition(
169+
name="Color",
170+
permissible_values=[PermissibleValue("RED"), PermissibleValue("BLUE")],
171+
),
172+
),
173+
(
174+
EnumDefinition(name="Color", permissible_values=[PermissibleValue("RED")]),
175+
[PermissibleValue("RED", description="A bright color"), "B"],
176+
EnumDefinition(
177+
name="Color",
178+
permissible_values=[
179+
PermissibleValue("RED", description="A bright color"),
180+
PermissibleValue("B"),
181+
],
182+
),
183+
),
184+
],
185+
)
186+
def test_add_enum_with_extra_permissible_values(
187+
enum_def: EnumDefinition,
188+
permissible_values: List[Union[str, PermissibleValue]],
189+
expected_added_enum: Optional[EnumDefinition],
190+
):
191+
"""
192+
Test adding an enum with extra, overriding, permissible values
193+
"""
194+
builder = SchemaBuilder()
195+
196+
if any(not isinstance(pv, (str, PermissibleValue)) for pv in permissible_values):
197+
with pytest.raises(TypeError, match="permissible value must be"):
198+
builder.add_enum(enum_def, permissible_values=permissible_values)
199+
else:
200+
builder.add_enum(enum_def, permissible_values=permissible_values)
201+
assert builder.schema.enums[enum_def.name] == expected_added_enum
202+
203+
204+
# === Tests for `SchemaBuilder.add_enum` ===
205+
@pytest.mark.parametrize(
206+
("enum_def", "extra_kwargs", "expected_added_enum"),
207+
[
208+
("Color", {}, EnumDefinition(name="Color")),
209+
(42, {}, None), # Invalid type for `enum_def`
210+
(
211+
"Color",
212+
{"description": "What meets the eyes"},
213+
EnumDefinition(name="Color", description="What meets the eyes"),
214+
),
215+
(
216+
{"name": "Color", "description": "It's obvious"},
217+
{"description": "What meets the eyes"},
218+
EnumDefinition(name="Color", description="What meets the eyes"),
219+
),
220+
(
221+
EnumDefinition("Color"),
222+
{"description": "What meets the eyes"},
223+
EnumDefinition(name="Color"),
224+
),
225+
(
226+
"Color",
227+
{"description": "What meets the eyes", "ijk": True}, # Invalid extra kwarg
228+
None,
229+
),
230+
],
231+
)
232+
def test_add_enum_with_extra_kwargs(
233+
enum_def: Union[EnumDefinition, dict, str],
234+
extra_kwargs: Dict[str, Any],
235+
expected_added_enum: Optional[EnumDefinition],
236+
):
237+
"""
238+
Test adding an enum with extra kwargs
239+
"""
240+
enum_meta_slots = {f.name for f in fields(EnumDefinition)}
241+
242+
builder = SchemaBuilder()
243+
244+
if not isinstance(enum_def, (str, dict, EnumDefinition)):
245+
with pytest.raises(TypeError, match="enum_def must be"):
246+
builder.add_enum(enum_def, **extra_kwargs)
247+
elif extra_kwargs.keys() - enum_meta_slots:
248+
# Handle the case of extra kwargs include a key that is not a meta slot of
249+
# `EnumDefinition`
250+
with pytest.raises(ValueError):
251+
builder.add_enum(enum_def, **extra_kwargs)
252+
else:
253+
builder.add_enum(enum_def, **extra_kwargs)
254+
255+
if isinstance(enum_def, str):
256+
enum_name = enum_def
257+
elif isinstance(enum_def, dict):
258+
enum_name = enum_def["name"]
259+
else:
260+
enum_name = enum_def.name
261+
262+
added_enum = builder.schema.enums[enum_name]
263+
264+
assert added_enum == expected_added_enum
265+
266+
# === Tests for `SchemaBuilder.add_enum` end ===

0 commit comments

Comments
 (0)