18574: Adds strict checking on conversion methods. Adds/updates tests.
[arvados.git] / sdk / python / arvados / vocabulary.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: Apache-2.0
4
5 import logging
6
7 from . import api
8
9 _logger = logging.getLogger('arvados.vocabulary')
10
11 def load_vocabulary(api_client=api('v1')):
12     """Load the Arvados vocabulary from the API.
13     """
14     return Vocabulary(api_client.vocabulary())
15
16 class Vocabulary(object):
17     def __init__(self, voc_definition={}):
18         self.strict_keys = voc_definition.get('strict_tags', False)
19         self.key_aliases = {}
20
21         for key_id, val in voc_definition.get('tags', {}).items():
22             strict = val.get('strict', False)
23             key_labels = [l['label'] for l in val.get('labels', [])]
24             values = {}
25             for v_id, v_val in val.get('values', {}).items():
26                 labels = [l['label'] for l in v_val.get('labels', [])]
27                 values[v_id] = VocabularyValue(v_id, labels)
28             vk = VocabularyKey(key_id, key_labels, values, strict)
29             self.key_aliases[key_id.lower()] = vk
30             for alias in vk.aliases:
31                 self.key_aliases[alias.lower()] = vk
32
33     def __getitem__(self, key):
34         return self.key_aliases[key.lower()]
35
36     def convert_to_identifiers(self, obj={}):
37         """Translate key/value pairs to machine readable identifiers.
38         """
39         if not isinstance(obj, dict):
40             raise ValueError("obj must be a dict")
41         r = {}
42         for k, v in obj.items():
43             k_id, v_id = k, v
44             try:
45                 k_id = self[k].identifier
46                 try:
47                     v_id = self[k][v].identifier
48                 except KeyError:
49                     if self[k].strict:
50                         raise ValueError("value '%s' not found for key '%s'" % (v, k))
51             except KeyError:
52                 if self.strict_keys:
53                     raise KeyError("key '%s' not found" % k)
54             r[k_id] = v_id
55         return r
56
57     def convert_to_labels(self, obj={}):
58         """Translate key/value pairs to human readable labels.
59         """
60         if not isinstance(obj, dict):
61             raise ValueError("obj must be a dict")
62         r = {}
63         for k, v in obj.items():
64             k_lbl, v_lbl = k, v
65             try:
66                 k_lbl = self[k].preferred_label
67                 try:
68                     v_lbl = self[k][v].preferred_label
69                 except KeyError:
70                     if self[k].strict:
71                         raise ValueError("value '%s' not found for key '%s'" % (v, k))
72             except KeyError:
73                 if self.strict_keys:
74                     raise KeyError("key '%s' not found" % k)
75             r[k_lbl] = v_lbl
76         return r
77
78 class VocabularyData(object):
79     def __init__(self, identifier, aliases=[]):
80         self.identifier = identifier
81         self.aliases = aliases
82
83     def __getattribute__(self, name):
84         if name == 'preferred_label':
85             return self.aliases[0]
86         return super(VocabularyData, self).__getattribute__(name)
87
88 class VocabularyValue(VocabularyData):
89     def __init__(self, identifier, aliases=[]):
90         super(VocabularyValue, self).__init__(identifier, aliases)
91
92 class VocabularyKey(VocabularyData):
93     def __init__(self, identifier, aliases=[], values={}, strict=False):
94         super(VocabularyKey, self).__init__(identifier, aliases)
95         self.strict = strict
96         self.value_aliases = {}
97         for v_id, v_val in values.items():
98             self.value_aliases[v_id.lower()] = v_val
99             for v_alias in v_val.aliases:
100                 self.value_aliases[v_alias.lower()] = v_val
101
102     def __getitem__(self, key):
103         return self.value_aliases[key.lower()]