Skip to content

Commit 46ff350

Browse files
authored
Change the behavior of all the getters in the Dictionary API when the corresponding key is not found (#490)
- The impacted classes are `DictionaryDomain`, `Dictionary` and `MetaData` - Instead of raising a KeyError, a ``None`` value is returned (like the dict behavior)
1 parent b919396 commit 46ff350

File tree

3 files changed

+72
-84
lines changed

3 files changed

+72
-84
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
- (`core`) New way to add a variable to a dictionary using a complete specification.
1616
- (`sklearn`) `Text` Khiops type support at the estimator level.
1717

18+
### Changed
19+
- (`core`) Dictionary API (DictionaryDomain, Dictionary, MetaData),
20+
when a requested key is not found in getters, return ``None`` instead
21+
of raising a `KeyError` exception.
22+
1823
### Fixed
1924
- (General) Inconsistency between the `tools.download_datasets` function and the
2025
current samples directory according to `core.api.get_samples_dir()`.

khiops/core/dictionary.py

Lines changed: 56 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -238,15 +238,10 @@ def get_dictionary(self, dictionary_name):
238238
Returns
239239
-------
240240
`Dictionary`
241-
The specified dictionary.
242-
243-
Raises
244-
------
245-
`KeyError`
246-
If no dictionary with the specified name exist.
247-
241+
The specified dictionary. ``None`` is returned if the dictionary name
242+
is not found.
248243
"""
249-
return self._dictionaries_by_name[dictionary_name]
244+
return self._dictionaries_by_name.get(dictionary_name)
250245

251246
def add_dictionary(self, dictionary):
252247
"""Adds a dictionary to this domain
@@ -409,34 +404,29 @@ def _get_dictionary_at_data_path_legacy(self, data_path):
409404
data_path_parts = data_path.split("`")
410405
source_dictionary_name = data_path_parts[0]
411406

412-
try:
413-
dictionary = self.get_dictionary(source_dictionary_name)
414-
except KeyError as error:
415-
raise ValueError(
416-
f"Source dictionary not found: '{source_dictionary_name}'"
417-
) from error
407+
dictionary = self.get_dictionary(source_dictionary_name)
408+
if dictionary is None:
409+
raise ValueError(f"Source dictionary not found: '{source_dictionary_name}'")
418410

419411
for table_variable_name in data_path_parts[1:]:
420-
try:
421-
table_variable = dictionary.get_variable(table_variable_name)
422-
except KeyError as error:
412+
table_variable = dictionary.get_variable(table_variable_name)
413+
if table_variable is None:
423414
raise ValueError(
424415
f"Table variable '{table_variable_name}' in data path not found"
425-
) from error
416+
)
426417

427418
if table_variable.type not in ["Table", "Entity"]:
428419
raise ValueError(
429420
f"Table variable '{table_variable_name}' "
430421
f"in data path is of type '{table_variable.type}'"
431422
)
432423

433-
try:
434-
dictionary = self.get_dictionary(table_variable.object_type)
435-
except KeyError as error:
424+
dictionary = self.get_dictionary(table_variable.object_type)
425+
if dictionary is None:
436426
raise ValueError(
437427
f"Table variable '{table_variable_name}' in data path "
438428
f"points to unknown dictionary '{table_variable.object_type}'"
439-
) from error
429+
)
440430
return dictionary
441431

442432
def _get_dictionary_at_data_path(self, data_path):
@@ -447,46 +437,47 @@ def _get_dictionary_at_data_path(self, data_path):
447437
# - either it is found as such,
448438
# - or it is a Table or Entity variable whose table needs to be looked-up
449439
first_table_variable_name = data_path_parts[0]
450-
try:
451-
dictionary = self.get_dictionary(first_table_variable_name)
452-
except KeyError as error:
440+
441+
dictionary = self.get_dictionary(first_table_variable_name)
442+
if dictionary is None:
453443
for a_dictionary in self.dictionaries:
454444
try:
455445
table_variable = a_dictionary.get_variable(
456446
first_table_variable_name
457447
)
458-
if table_variable.type not in ["Table", "Entity"]:
459-
raise ValueError from error
460-
dictionary = self.get_dictionary(table_variable.object_type)
461-
break
462-
except (KeyError, ValueError):
448+
if table_variable is not None:
449+
if table_variable.type not in ["Table", "Entity"]:
450+
raise ValueError(
451+
f"Variable '{table_variable}' "
452+
"must be of type 'Table' or 'Entity'"
453+
)
454+
dictionary = self.get_dictionary(table_variable.object_type)
455+
if dictionary is not None:
456+
break
457+
except ValueError:
463458
continue
464459
else:
465-
raise ValueError(
466-
f"Dictionary not found in data path: '{data_path}'"
467-
) from error
460+
raise ValueError(f"Dictionary not found in data path: '{data_path}'")
468461

469462
for table_variable_name in data_path_parts[1:]:
470-
try:
471-
table_variable = dictionary.get_variable(table_variable_name)
472-
except KeyError as error:
463+
table_variable = dictionary.get_variable(table_variable_name)
464+
if table_variable is None:
473465
raise ValueError(
474466
f"Table variable '{table_variable_name}' in data path not found"
475-
) from error
467+
)
476468

477469
if table_variable.type not in ["Table", "Entity"]:
478470
raise ValueError(
479471
f"Table variable '{table_variable_name}' "
480472
f"in data path is of type '{table_variable.type}'"
481473
)
482474

483-
try:
484-
dictionary = self.get_dictionary(table_variable.object_type)
485-
except KeyError as error:
475+
dictionary = self.get_dictionary(table_variable.object_type)
476+
if dictionary is None:
486477
raise ValueError(
487478
f"Table variable '{table_variable_name}' in data path "
488479
f"points to unknown dictionary '{table_variable.object_type}'"
489-
) from error
480+
)
490481
return dictionary
491482

492483
def export_khiops_dictionary_file(self, kdic_file_path):
@@ -740,10 +731,11 @@ def copy(self):
740731
def get_value(self, key):
741732
"""Returns the metadata value associated to the specified key
742733
743-
Raises
744-
------
745-
`KeyError`
746-
If the key is not found
734+
Returns
735+
-------
736+
`Metadata`
737+
Metadata value associated to the specified key. ``None`` is returned
738+
if the metadata key is not found.
747739
"""
748740
return self.meta_data.get_value(key)
749741

@@ -770,14 +762,10 @@ def get_variable(self, variable_name):
770762
Returns
771763
-------
772764
`Variable`
773-
The specified variable.
774-
775-
Raises
776-
------
777-
`KeyError`
778-
If no variable with the specified name exists.
765+
The specified variable. ``None`` is returned if the variable name is
766+
not found.
779767
"""
780-
return self._variables_by_name[variable_name]
768+
return self._variables_by_name.get(variable_name)
781769

782770
def get_variable_block(self, variable_block_name):
783771
"""Returns the specified variable block
@@ -790,15 +778,10 @@ def get_variable_block(self, variable_block_name):
790778
Returns
791779
-------
792780
`VariableBlock`
793-
The specified variable.
794-
795-
Raises
796-
------
797-
`KeyError`
798-
If no variable block with the specified name exists.
799-
781+
The specified variable block. ``None`` is returned if the variable
782+
block name is not found.
800783
"""
801-
return self._variable_blocks_by_name[variable_block_name]
784+
return self._variable_blocks_by_name.get(variable_block_name)
802785

803786
def add_variable(self, variable):
804787
"""Adds a variable to this dictionary
@@ -1282,10 +1265,11 @@ def copy(self):
12821265
def get_value(self, key):
12831266
"""Returns the metadata value associated to the specified key
12841267
1285-
Raises
1286-
------
1287-
`KeyError`
1288-
If no metadata has this key.
1268+
Returns
1269+
-------
1270+
`Metadata`
1271+
Metadata value associated to the specified key. ``None`` is returned
1272+
if the metadata key is not found.
12891273
"""
12901274
return self.meta_data.get_value(key)
12911275

@@ -1541,10 +1525,11 @@ def remove_variable(self, variable):
15411525
def get_value(self, key):
15421526
"""Returns the metadata value associated to the specified key
15431527
1544-
Raises
1545-
------
1546-
`KeyError`
1547-
If ``key`` is not found
1528+
Returns
1529+
-------
1530+
`Metadata`
1531+
Metadata value associated to the specified key. ``None`` is returned
1532+
if the metadata key is not found.
15481533
"""
15491534
return self.meta_data.get_value(key)
15501535

@@ -2019,14 +2004,13 @@ def get_value(self, key):
20192004
Returns
20202005
-------
20212006
int, str or float
2022-
The value at the specified key
2007+
The value at the specified key. ``None`` is returned if the key is
2008+
not found.
20232009
20242010
Raises
20252011
------
20262012
`TypeError`
20272013
If ``key`` is not str.
2028-
`KeyError`
2029-
If ``key`` is not found.
20302014
"""
20312015
# Check the argument types
20322016
if not is_string_like(key):
@@ -2036,7 +2020,7 @@ def get_value(self, key):
20362020
for i, stored_key in enumerate(self.keys):
20372021
if stored_key == key:
20382022
return self.values[i]
2039-
raise KeyError(key)
2023+
return None
20402024

20412025
def add_value(self, key, value):
20422026
"""Adds a value at the specified key

tests/test_core.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,8 +1796,7 @@ def test_dictionary_simple_edge_cases(self):
17961796
with self.assertRaises(TypeError):
17971797
meta_data.remove_key(object())
17981798
meta_data.add_value("key", "value")
1799-
with self.assertRaises(KeyError):
1800-
meta_data.get_value("INEXISTENT KEY")
1799+
self.assertIsNone(meta_data.get_value("INEXISTENT KEY"))
18011800
with self.assertRaises(ValueError):
18021801
meta_data.add_value("key", "REPEATED KEY")
18031802
with self.assertRaises(KeyError):
@@ -1967,8 +1966,8 @@ def test_dictionary_accessors(self):
19671966
self.assertEqual(block, removed_block)
19681967
self.assertIsNone(block_variable.variable_block)
19691968
self.assertEqual(block.variables, [])
1970-
with self.assertRaises(KeyError):
1971-
dictionary_copy.get_variable_block(block.name)
1969+
# Nonexistent variable block name
1970+
self.assertIsNone(dictionary_copy.get_variable_block(block.name))
19721971

19731972
# Add and remove the block and remove the native variables
19741973
dictionary_copy.remove_variable(block_variable.name)
@@ -1981,10 +1980,10 @@ def test_dictionary_accessors(self):
19811980
self.assertEqual(block, removed_block)
19821981
self.assertEqual(block.variables, [block_variable])
19831982
self.assertEqual(block_variable.block, removed_block)
1984-
with self.assertRaises(KeyError):
1985-
dictionary_copy.get_variable(block_variable.name)
1986-
with self.assertRaises(KeyError):
1987-
dictionary_copy.get_variable_block(block.name)
1983+
# Nonexistent variable block name
1984+
self.assertIsNone(dictionary_copy.get_variable(block_variable.name))
1985+
# Nonexistent variable name
1986+
self.assertIsNone(dictionary_copy.get_variable_block(block.name))
19881987

19891988
# Set the block as non-native add, and remove it
19901989
dictionary_copy.add_variable_block(block)
@@ -1999,10 +1998,10 @@ def test_dictionary_accessors(self):
19991998
self.assertEqual(block, removed_block)
20001999
self.assertEqual(block.variables, [block_variable])
20012000
self.assertEqual(block_variable.block, removed_block)
2002-
with self.assertRaises(KeyError):
2003-
dictionary_copy.get_variable(block_variable.name)
2004-
with self.assertRaises(KeyError):
2005-
dictionary_copy.get_variable_block(block.name)
2001+
# Nonexistent variable block name
2002+
self.assertIsNone(dictionary_copy.get_variable(block_variable.name))
2003+
# Nonexistent variable name
2004+
self.assertIsNone(dictionary_copy.get_variable_block(block.name))
20062005

20072006
# Test Dictionary variable and block accessors by cleaning the dict.
20082007
for variable_name in [

0 commit comments

Comments
 (0)