[ARVADOS] updated: 2.1.0-1966-gee24e4652

Git user git at public.arvados.org
Mon Feb 28 21:34:15 UTC 2022


Summary of changes:
 doc/api/index.html.textile.liquid   |   4 +
 sdk/python/arvados/vocabulary.py    | 103 +++++++++++++++++
 sdk/python/tests/test_vocabulary.py | 219 ++++++++++++++++++++++++++++++++++++
 3 files changed, 326 insertions(+)
 create mode 100644 sdk/python/arvados/vocabulary.py
 create mode 100644 sdk/python/tests/test_vocabulary.py

       via  ee24e465200ab83e7bbf5d570086a554eb1d41d4 (commit)
       via  23656211a6d871b1c7d3022665a3f2d257da8ce3 (commit)
       via  af0abaaa09186f273f02fb41a7f2249cceb1d22d (commit)
       via  f5c53282fce8ade8d9ac24a11c0e33fa900aee45 (commit)
       via  4f96167fade569a1e6a6aec0d0a4495442a91c33 (commit)
       via  6b703b56ec5401394e1c446f5b9d0880006c21f8 (commit)
       via  724c78fdbb4b489d1194ea8ded812dd8429a1c7d (commit)
      from  c13622daf14f1d46bbea25e8e0255fb95f00cebd (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit ee24e465200ab83e7bbf5d570086a554eb1d41d4
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Feb 28 18:03:02 2022 -0300

    18574: Adds some more test cases on convert_to_* methods.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/sdk/python/tests/test_vocabulary.py b/sdk/python/tests/test_vocabulary.py
index a3b6391b0..6274fecaf 100644
--- a/sdk/python/tests/test_vocabulary.py
+++ b/sdk/python/tests/test_vocabulary.py
@@ -59,6 +59,13 @@ class VocabularyTest(unittest.TestCase):
                     },
                 },
             },
+            'IDTAGCOMMENTS': {
+                'strict': False,
+                'labels': [
+                    {'label': 'Comment'},
+                    {'label': 'Notes'},
+                ],
+            },
         },
     }
 
@@ -72,7 +79,8 @@ class VocabularyTest(unittest.TestCase):
         self.assertEqual(
             self.voc.key_aliases.keys(),
             set(['idtaganimals', 'creature', 'animal',
-                'idtagimportances', 'importance', 'priority'])
+                'idtagimportances', 'importance', 'priority',
+                'idtagcomments', 'comment', 'notes'])
         )
 
         vk = self.voc.key_aliases['creature']
@@ -127,15 +135,15 @@ class VocabularyTest(unittest.TestCase):
 
     def test_convert_to_identifiers_multiple_pairs(self):
         cases = [
-            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1'},
-            {'IDTAGIMPORTANCES': 'High', 'IDTAGANIMALS': 'IDVALANIMAL1'},
-            {'importance': 'IDVALIMPORTANCE1', 'animal': 'IDVALANIMAL1'},
-            {'priority': 'high priority', 'animal': 'IDVALANIMAL1'},
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1', 'IDTAGCOMMENTS': 'Very important person'},
+            {'IDTAGIMPORTANCES': 'High', 'IDTAGANIMALS': 'IDVALANIMAL1', 'comment': 'Very important person'},
+            {'importance': 'IDVALIMPORTANCE1', 'animal': 'IDVALANIMAL1', 'notes': 'Very important person'},
+            {'priority': 'high priority', 'animal': 'IDVALANIMAL1', 'NOTES': 'Very important person'},
         ]
         for case in cases:
             self.assertEqual(
                 self.voc.convert_to_identifiers(case),
-                {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1'},
+                {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1', 'IDTAGCOMMENTS': 'Very important person'},
                 "failing test case: {}".format(case)
             )
 
@@ -174,18 +182,23 @@ class VocabularyTest(unittest.TestCase):
 
     def test_convert_to_labels_multiple_pairs(self):
         cases = [
-            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1'},
-            {'IDTAGIMPORTANCES': 'High', 'IDTAGANIMALS': 'IDVALANIMAL1'},
-            {'importance': 'IDVALIMPORTANCE1', 'animal': 'IDVALANIMAL1'},
-            {'priority': 'high priority', 'animal': 'IDVALANIMAL1'},
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1', 'IDTAGCOMMENTS': 'Very important person'},
+            {'IDTAGIMPORTANCES': 'High', 'IDTAGANIMALS': 'IDVALANIMAL1', 'comment': 'Very important person'},
+            {'importance': 'IDVALIMPORTANCE1', 'animal': 'IDVALANIMAL1', 'notes': 'Very important person'},
+            {'priority': 'high priority', 'animal': 'IDVALANIMAL1', 'NOTES': 'Very important person'},
         ]
         for case in cases:
             self.assertEqual(
                 self.voc.convert_to_labels(case),
-                {'Importance': 'High', 'Animal': 'Human'},
+                {'Importance': 'High', 'Animal': 'Human', 'Comment': 'Very important person'},
                 "failing test case: {}".format(case)
             )
 
+    def test_convert_roundtrip(self):
+        initial = {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1', 'IDTAGCOMMENTS': 'Very important person'}
+        converted = self.voc.convert_to_labels(initial)
+        self.assertEqual(self.voc.convert_to_identifiers(converted), initial)
+
     def test_convert_to_labels_unknown_key(self):
         # Non-strict vocabulary
         self.assertEqual(self.voc.strict_keys, False)

commit 23656211a6d871b1c7d3022665a3f2d257da8ce3
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Feb 28 17:51:29 2022 -0300

    18574: Adds strict checking on conversion methods. Adds/updates tests.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/sdk/python/arvados/vocabulary.py b/sdk/python/arvados/vocabulary.py
index b4148890a..d51435f03 100644
--- a/sdk/python/arvados/vocabulary.py
+++ b/sdk/python/arvados/vocabulary.py
@@ -46,9 +46,11 @@ class Vocabulary(object):
                 try:
                     v_id = self[k][v].identifier
                 except KeyError:
-                    pass
+                    if self[k].strict:
+                        raise ValueError("value '%s' not found for key '%s'" % (v, k))
             except KeyError:
-                pass
+                if self.strict_keys:
+                    raise KeyError("key '%s' not found" % k)
             r[k_id] = v_id
         return r
 
@@ -65,9 +67,11 @@ class Vocabulary(object):
                 try:
                     v_lbl = self[k][v].preferred_label
                 except KeyError:
-                    pass
+                    if self[k].strict:
+                        raise ValueError("value '%s' not found for key '%s'" % (v, k))
             except KeyError:
-                pass
+                if self.strict_keys:
+                    raise KeyError("key '%s' not found" % k)
             r[k_lbl] = v_lbl
         return r
 
diff --git a/sdk/python/tests/test_vocabulary.py b/sdk/python/tests/test_vocabulary.py
index 7cca66a1c..a3b6391b0 100644
--- a/sdk/python/tests/test_vocabulary.py
+++ b/sdk/python/tests/test_vocabulary.py
@@ -32,7 +32,7 @@ class VocabularyTest(unittest.TestCase):
                     },
                 },
             },
-            'IDTAGIMPORTANCE': {
+            'IDTAGIMPORTANCES': {
                 'strict': True,
                 'labels': [
                     {'label': 'Importance'},
@@ -72,7 +72,7 @@ class VocabularyTest(unittest.TestCase):
         self.assertEqual(
             self.voc.key_aliases.keys(),
             set(['idtaganimals', 'creature', 'animal',
-                'idtagimportance', 'importance', 'priority'])
+                'idtagimportances', 'importance', 'priority'])
         )
 
         vk = self.voc.key_aliases['creature']
@@ -113,22 +113,55 @@ class VocabularyTest(unittest.TestCase):
 
     def test_convert_to_identifiers(self):
         cases = [
-            {'IDTAGIMPORTANCE': 'IDVALIMPORTANCE1'},
-            {'IDTAGIMPORTANCE': 'High'},
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1'},
+            {'IDTAGIMPORTANCES': 'High'},
             {'importance': 'IDVALIMPORTANCE1'},
             {'priority': 'high priority'},
         ]
         for case in cases:
             self.assertEqual(
                 self.voc.convert_to_identifiers(case),
-                {'IDTAGIMPORTANCE': 'IDVALIMPORTANCE1'},
+                {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1'},
                 "failing test case: {}".format(case)
             )
 
+    def test_convert_to_identifiers_multiple_pairs(self):
+        cases = [
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1'},
+            {'IDTAGIMPORTANCES': 'High', 'IDTAGANIMALS': 'IDVALANIMAL1'},
+            {'importance': 'IDVALIMPORTANCE1', 'animal': 'IDVALANIMAL1'},
+            {'priority': 'high priority', 'animal': 'IDVALANIMAL1'},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_identifiers(case),
+                {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1'},
+                "failing test case: {}".format(case)
+            )
+
+    def test_convert_to_identifiers_unknown_key(self):
+        # Non-strict vocabulary
+        self.assertEqual(self.voc.strict_keys, False)
+        self.assertEqual(self.voc.convert_to_identifiers({'foo': 'bar'}), {'foo': 'bar'})
+        # Strict vocabulary
+        strict_voc = arvados.vocabulary.Vocabulary(self.EXAMPLE_VOC)
+        strict_voc.strict_keys = True
+        with self.assertRaises(KeyError):
+            strict_voc.convert_to_identifiers({'foo': 'bar'})
+
+    def test_convert_to_identifiers_unknown_value(self):
+        # Non-strict key
+        self.assertEqual(self.voc['animal'].strict, False)
+        self.assertEqual(self.voc.convert_to_identifiers({'Animal': 'foo'}), {'IDTAGANIMALS': 'foo'})
+        # Strict key
+        self.assertEqual(self.voc['priority'].strict, True)
+        with self.assertRaises(ValueError):
+            self.voc.convert_to_identifiers({'Priority': 'foo'})
+
     def test_convert_to_labels(self):
         cases = [
-            {'IDTAGIMPORTANCE': 'IDVALIMPORTANCE1'},
-            {'IDTAGIMPORTANCE': 'High'},
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1'},
+            {'IDTAGIMPORTANCES': 'High'},
             {'importance': 'IDVALIMPORTANCE1'},
             {'priority': 'high priority'},
         ]
@@ -137,4 +170,37 @@ class VocabularyTest(unittest.TestCase):
                 self.voc.convert_to_labels(case),
                 {'Importance': 'High'},
                 "failing test case: {}".format(case)
-            )
\ No newline at end of file
+            )
+
+    def test_convert_to_labels_multiple_pairs(self):
+        cases = [
+            {'IDTAGIMPORTANCES': 'IDVALIMPORTANCE1', 'IDTAGANIMALS': 'IDVALANIMAL1'},
+            {'IDTAGIMPORTANCES': 'High', 'IDTAGANIMALS': 'IDVALANIMAL1'},
+            {'importance': 'IDVALIMPORTANCE1', 'animal': 'IDVALANIMAL1'},
+            {'priority': 'high priority', 'animal': 'IDVALANIMAL1'},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_labels(case),
+                {'Importance': 'High', 'Animal': 'Human'},
+                "failing test case: {}".format(case)
+            )
+
+    def test_convert_to_labels_unknown_key(self):
+        # Non-strict vocabulary
+        self.assertEqual(self.voc.strict_keys, False)
+        self.assertEqual(self.voc.convert_to_labels({'foo': 'bar'}), {'foo': 'bar'})
+        # Strict vocabulary
+        strict_voc = arvados.vocabulary.Vocabulary(self.EXAMPLE_VOC)
+        strict_voc.strict_keys = True
+        with self.assertRaises(KeyError):
+            strict_voc.convert_to_labels({'foo': 'bar'})
+
+    def test_convert_to_labels_unknown_value(self):
+        # Non-strict key
+        self.assertEqual(self.voc['animal'].strict, False)
+        self.assertEqual(self.voc.convert_to_labels({'IDTAGANIMALS': 'foo'}), {'Animal': 'foo'})
+        # Strict key
+        self.assertEqual(self.voc['priority'].strict, True)
+        with self.assertRaises(ValueError):
+            self.voc.convert_to_labels({'IDTAGIMPORTANCES': 'foo'})
\ No newline at end of file

commit af0abaaa09186f273f02fb41a7f2249cceb1d22d
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Feb 28 17:20:49 2022 -0300

    18574: Adds conversion methods. Improves & adds tests.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/sdk/python/arvados/vocabulary.py b/sdk/python/arvados/vocabulary.py
index 1791566b5..b4148890a 100644
--- a/sdk/python/arvados/vocabulary.py
+++ b/sdk/python/arvados/vocabulary.py
@@ -26,13 +26,51 @@ class Vocabulary(object):
                 labels = [l['label'] for l in v_val.get('labels', [])]
                 values[v_id] = VocabularyValue(v_id, labels)
             vk = VocabularyKey(key_id, key_labels, values, strict)
-            self.key_aliases[key_id] = vk
+            self.key_aliases[key_id.lower()] = vk
             for alias in vk.aliases:
                 self.key_aliases[alias.lower()] = vk
 
     def __getitem__(self, key):
         return self.key_aliases[key.lower()]
 
+    def convert_to_identifiers(self, obj={}):
+        """Translate key/value pairs to machine readable identifiers.
+        """
+        if not isinstance(obj, dict):
+            raise ValueError("obj must be a dict")
+        r = {}
+        for k, v in obj.items():
+            k_id, v_id = k, v
+            try:
+                k_id = self[k].identifier
+                try:
+                    v_id = self[k][v].identifier
+                except KeyError:
+                    pass
+            except KeyError:
+                pass
+            r[k_id] = v_id
+        return r
+
+    def convert_to_labels(self, obj={}):
+        """Translate key/value pairs to human readable labels.
+        """
+        if not isinstance(obj, dict):
+            raise ValueError("obj must be a dict")
+        r = {}
+        for k, v in obj.items():
+            k_lbl, v_lbl = k, v
+            try:
+                k_lbl = self[k].preferred_label
+                try:
+                    v_lbl = self[k][v].preferred_label
+                except KeyError:
+                    pass
+            except KeyError:
+                pass
+            r[k_lbl] = v_lbl
+        return r
+
 class VocabularyData(object):
     def __init__(self, identifier, aliases=[]):
         self.identifier = identifier
@@ -53,7 +91,7 @@ class VocabularyKey(VocabularyData):
         self.strict = strict
         self.value_aliases = {}
         for v_id, v_val in values.items():
-            self.value_aliases[v_id] = v_val
+            self.value_aliases[v_id.lower()] = v_val
             for v_alias in v_val.aliases:
                 self.value_aliases[v_alias.lower()] = v_val
 
diff --git a/sdk/python/tests/test_vocabulary.py b/sdk/python/tests/test_vocabulary.py
index ccaa7fe88..7cca66a1c 100644
--- a/sdk/python/tests/test_vocabulary.py
+++ b/sdk/python/tests/test_vocabulary.py
@@ -62,44 +62,79 @@ class VocabularyTest(unittest.TestCase):
         },
     }
 
-    def perform_vocabulary_tests(self, voc):
-        self.assertEqual(voc.strict_keys, False)
+    def setUp(self):
+        self.api = arvados.api('v1')
+        self.voc = vocabulary.Vocabulary(self.EXAMPLE_VOC)
+        self.api.vocabulary = mock.MagicMock(return_value=self.EXAMPLE_VOC)
+
+    def test_vocabulary_keys(self):
+        self.assertEqual(self.voc.strict_keys, False)
         self.assertEqual(
-            voc.key_aliases.keys(),
-            set(['IDTAGANIMALS', 'creature', 'animal',
-                'IDTAGIMPORTANCE', 'importance', 'priority'])
+            self.voc.key_aliases.keys(),
+            set(['idtaganimals', 'creature', 'animal',
+                'idtagimportance', 'importance', 'priority'])
         )
 
-        vk = voc.key_aliases['creature']
+        vk = self.voc.key_aliases['creature']
         self.assertEqual(vk.strict, False)
         self.assertEqual(vk.identifier, 'IDTAGANIMALS')
         self.assertEqual(vk.aliases, ['Animal', 'Creature'])
         self.assertEqual(vk.preferred_label, 'Animal')
+        self.assertEqual(
+            vk.value_aliases.keys(),
+            set(['idvalanimal1', 'human', 'homo sapiens',
+                'idvalanimal2', 'elephant', 'loxodonta'])
+        )
 
+    def test_vocabulary_values(self):
+        vk = self.voc.key_aliases['creature']
         vv = vk.value_aliases['human']
         self.assertEqual(vv.identifier, 'IDVALANIMAL1')
         self.assertEqual(vv.aliases, ['Human', 'Homo sapiens'])
         self.assertEqual(vv.preferred_label, 'Human')
 
-        self.assertEqual(voc['creature']['human'].identifier, vv.identifier)
-        self.assertEqual(voc['Creature']['Human'].identifier, vv.identifier)
-        self.assertEqual(voc['CREATURE']['HUMAN'].identifier, vv.identifier)
+    def test_vocabulary_indexing(self):
+        self.assertEqual(self.voc['creature']['human'].identifier, 'IDVALANIMAL1')
+        self.assertEqual(self.voc['Creature']['Human'].identifier, 'IDVALANIMAL1')
+        self.assertEqual(self.voc['CREATURE']['HUMAN'].identifier, 'IDVALANIMAL1')
         with self.assertRaises(KeyError):
-            inexistant = voc['foo']
+            inexistant = self.voc['foo']
 
     def test_empty_vocabulary(self):
-        voc = vocabulary.Vocabulary()
+        voc = vocabulary.Vocabulary({})
         self.assertEqual(voc.strict_keys, False)
         self.assertEqual(voc.key_aliases, {})
 
-    def test_vocabulary_explicit_instantiation(self):
-        voc = vocabulary.Vocabulary(self.EXAMPLE_VOC)
-        self.perform_vocabulary_tests(voc)
+    def test_load_vocabulary_with_api(self):
+        voc = vocabulary.load_vocabulary(self.api)
+        self.assertEqual(voc['creature']['human'].identifier, 'IDVALANIMAL1')
+        self.assertEqual(voc['Creature']['Human'].identifier, 'IDVALANIMAL1')
+        self.assertEqual(voc['CREATURE']['HUMAN'].identifier, 'IDVALANIMAL1')
 
-    @mock.patch('arvados.api')
-    def test_load_vocabulary_with_api(self, api_mock):
-        api_mock.return_value = mock.MagicMock()
-        api_mock.return_value.vocabulary.return_value = self.EXAMPLE_VOC
+    def test_convert_to_identifiers(self):
+        cases = [
+            {'IDTAGIMPORTANCE': 'IDVALIMPORTANCE1'},
+            {'IDTAGIMPORTANCE': 'High'},
+            {'importance': 'IDVALIMPORTANCE1'},
+            {'priority': 'high priority'},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_identifiers(case),
+                {'IDTAGIMPORTANCE': 'IDVALIMPORTANCE1'},
+                "failing test case: {}".format(case)
+            )
 
-        voc = vocabulary.load_vocabulary(arvados.api('v1'))
-        self.perform_vocabulary_tests(voc)
+    def test_convert_to_labels(self):
+        cases = [
+            {'IDTAGIMPORTANCE': 'IDVALIMPORTANCE1'},
+            {'IDTAGIMPORTANCE': 'High'},
+            {'importance': 'IDVALIMPORTANCE1'},
+            {'priority': 'high priority'},
+        ]
+        for case in cases:
+            self.assertEqual(
+                self.voc.convert_to_labels(case),
+                {'Importance': 'High'},
+                "failing test case: {}".format(case)
+            )
\ No newline at end of file

commit f5c53282fce8ade8d9ac24a11c0e33fa900aee45
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Feb 28 15:50:37 2022 -0300

    18574: Adds 'preferred_label' virtual attribute. Avoids storing the voc twice.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/sdk/python/arvados/vocabulary.py b/sdk/python/arvados/vocabulary.py
index 643e167e6..1791566b5 100644
--- a/sdk/python/arvados/vocabulary.py
+++ b/sdk/python/arvados/vocabulary.py
@@ -15,8 +15,7 @@ def load_vocabulary(api_client=api('v1')):
 
 class Vocabulary(object):
     def __init__(self, voc_definition={}):
-        self._definition = voc_definition
-        self.strict_keys = self._definition.get('strict_tags', False)
+        self.strict_keys = voc_definition.get('strict_tags', False)
         self.key_aliases = {}
 
         for key_id, val in voc_definition.get('tags', {}).items():
@@ -39,6 +38,11 @@ class VocabularyData(object):
         self.identifier = identifier
         self.aliases = aliases
 
+    def __getattribute__(self, name):
+        if name == 'preferred_label':
+            return self.aliases[0]
+        return super(VocabularyData, self).__getattribute__(name)
+
 class VocabularyValue(VocabularyData):
     def __init__(self, identifier, aliases=[]):
         super(VocabularyValue, self).__init__(identifier, aliases)
diff --git a/sdk/python/tests/test_vocabulary.py b/sdk/python/tests/test_vocabulary.py
index 49fd60126..ccaa7fe88 100644
--- a/sdk/python/tests/test_vocabulary.py
+++ b/sdk/python/tests/test_vocabulary.py
@@ -74,14 +74,18 @@ class VocabularyTest(unittest.TestCase):
         self.assertEqual(vk.strict, False)
         self.assertEqual(vk.identifier, 'IDTAGANIMALS')
         self.assertEqual(vk.aliases, ['Animal', 'Creature'])
+        self.assertEqual(vk.preferred_label, 'Animal')
 
         vv = vk.value_aliases['human']
         self.assertEqual(vv.identifier, 'IDVALANIMAL1')
         self.assertEqual(vv.aliases, ['Human', 'Homo sapiens'])
+        self.assertEqual(vv.preferred_label, 'Human')
 
         self.assertEqual(voc['creature']['human'].identifier, vv.identifier)
         self.assertEqual(voc['Creature']['Human'].identifier, vv.identifier)
         self.assertEqual(voc['CREATURE']['HUMAN'].identifier, vv.identifier)
+        with self.assertRaises(KeyError):
+            inexistant = voc['foo']
 
     def test_empty_vocabulary(self):
         voc = vocabulary.Vocabulary()

commit 4f96167fade569a1e6a6aec0d0a4495442a91c33
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Feb 28 15:39:49 2022 -0300

    18574: Fixes key_aliases/value_aliases indexing. Adds dict-style indexing.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/sdk/python/arvados/vocabulary.py b/sdk/python/arvados/vocabulary.py
index 8d89746ca..643e167e6 100644
--- a/sdk/python/arvados/vocabulary.py
+++ b/sdk/python/arvados/vocabulary.py
@@ -26,12 +26,18 @@ class Vocabulary(object):
             for v_id, v_val in val.get('values', {}).items():
                 labels = [l['label'] for l in v_val.get('labels', [])]
                 values[v_id] = VocabularyValue(v_id, labels)
-            self.key_aliases[key_id] = VocabularyKey(key_id, key_labels, values, strict)
+            vk = VocabularyKey(key_id, key_labels, values, strict)
+            self.key_aliases[key_id] = vk
+            for alias in vk.aliases:
+                self.key_aliases[alias.lower()] = vk
+
+    def __getitem__(self, key):
+        return self.key_aliases[key.lower()]
 
 class VocabularyData(object):
     def __init__(self, identifier, aliases=[]):
         self.identifier = identifier
-        self.aliases = set([x.lower() for x in aliases])
+        self.aliases = aliases
 
 class VocabularyValue(VocabularyData):
     def __init__(self, identifier, aliases=[]):
@@ -40,5 +46,12 @@ class VocabularyValue(VocabularyData):
 class VocabularyKey(VocabularyData):
     def __init__(self, identifier, aliases=[], values={}, strict=False):
         super(VocabularyKey, self).__init__(identifier, aliases)
-        self.values = values
-        self.strict = strict
\ No newline at end of file
+        self.strict = strict
+        self.value_aliases = {}
+        for v_id, v_val in values.items():
+            self.value_aliases[v_id] = v_val
+            for v_alias in v_val.aliases:
+                self.value_aliases[v_alias.lower()] = v_val
+
+    def __getitem__(self, key):
+        return self.value_aliases[key.lower()]
\ No newline at end of file
diff --git a/sdk/python/tests/test_vocabulary.py b/sdk/python/tests/test_vocabulary.py
index 7aea129af..49fd60126 100644
--- a/sdk/python/tests/test_vocabulary.py
+++ b/sdk/python/tests/test_vocabulary.py
@@ -64,19 +64,31 @@ class VocabularyTest(unittest.TestCase):
 
     def perform_vocabulary_tests(self, voc):
         self.assertEqual(voc.strict_keys, False)
-        self.assertEqual(voc.key_aliases.keys(), set(['IDTAGANIMALS', 'IDTAGIMPORTANCE']))
+        self.assertEqual(
+            voc.key_aliases.keys(),
+            set(['IDTAGANIMALS', 'creature', 'animal',
+                'IDTAGIMPORTANCE', 'importance', 'priority'])
+        )
 
-        self.assertEqual(voc.key_aliases['IDTAGANIMALS'].strict, False)
-        self.assertEqual(set(voc.key_aliases['IDTAGANIMALS'].aliases), set(['animal', 'creature']))
-        self.assertEqual(voc.key_aliases['IDTAGANIMALS'].values.keys(), set(['IDVALANIMAL1', 'IDVALANIMAL2']))
-        self.assertEqual(voc.key_aliases['IDTAGANIMALS'].values['IDVALANIMAL1'].aliases, set(['human', 'homo sapiens']))
+        vk = voc.key_aliases['creature']
+        self.assertEqual(vk.strict, False)
+        self.assertEqual(vk.identifier, 'IDTAGANIMALS')
+        self.assertEqual(vk.aliases, ['Animal', 'Creature'])
+
+        vv = vk.value_aliases['human']
+        self.assertEqual(vv.identifier, 'IDVALANIMAL1')
+        self.assertEqual(vv.aliases, ['Human', 'Homo sapiens'])
+
+        self.assertEqual(voc['creature']['human'].identifier, vv.identifier)
+        self.assertEqual(voc['Creature']['Human'].identifier, vv.identifier)
+        self.assertEqual(voc['CREATURE']['HUMAN'].identifier, vv.identifier)
 
     def test_empty_vocabulary(self):
         voc = vocabulary.Vocabulary()
         self.assertEqual(voc.strict_keys, False)
         self.assertEqual(voc.key_aliases, {})
 
-    def test_load_vocabulary(self):
+    def test_vocabulary_explicit_instantiation(self):
         voc = vocabulary.Vocabulary(self.EXAMPLE_VOC)
         self.perform_vocabulary_tests(voc)
 

commit 6b703b56ec5401394e1c446f5b9d0880006c21f8
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Feb 28 13:14:32 2022 -0300

    18574: Initial vocabulary loading support & tests.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/sdk/python/arvados/vocabulary.py b/sdk/python/arvados/vocabulary.py
new file mode 100644
index 000000000..8d89746ca
--- /dev/null
+++ b/sdk/python/arvados/vocabulary.py
@@ -0,0 +1,44 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import logging
+
+from . import api
+
+_logger = logging.getLogger('arvados.vocabulary')
+
+def load_vocabulary(api_client=api('v1')):
+    """Load the Arvados vocabulary from the API.
+    """
+    return Vocabulary(api_client.vocabulary())
+
+class Vocabulary(object):
+    def __init__(self, voc_definition={}):
+        self._definition = voc_definition
+        self.strict_keys = self._definition.get('strict_tags', False)
+        self.key_aliases = {}
+
+        for key_id, val in voc_definition.get('tags', {}).items():
+            strict = val.get('strict', False)
+            key_labels = [l['label'] for l in val.get('labels', [])]
+            values = {}
+            for v_id, v_val in val.get('values', {}).items():
+                labels = [l['label'] for l in v_val.get('labels', [])]
+                values[v_id] = VocabularyValue(v_id, labels)
+            self.key_aliases[key_id] = VocabularyKey(key_id, key_labels, values, strict)
+
+class VocabularyData(object):
+    def __init__(self, identifier, aliases=[]):
+        self.identifier = identifier
+        self.aliases = set([x.lower() for x in aliases])
+
+class VocabularyValue(VocabularyData):
+    def __init__(self, identifier, aliases=[]):
+        super(VocabularyValue, self).__init__(identifier, aliases)
+
+class VocabularyKey(VocabularyData):
+    def __init__(self, identifier, aliases=[], values={}, strict=False):
+        super(VocabularyKey, self).__init__(identifier, aliases)
+        self.values = values
+        self.strict = strict
\ No newline at end of file
diff --git a/sdk/python/tests/test_vocabulary.py b/sdk/python/tests/test_vocabulary.py
new file mode 100644
index 000000000..7aea129af
--- /dev/null
+++ b/sdk/python/tests/test_vocabulary.py
@@ -0,0 +1,89 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import arvados
+import unittest
+import mock
+
+from arvados import api, vocabulary
+
+class VocabularyTest(unittest.TestCase):
+    EXAMPLE_VOC = {
+        'tags': {
+            'IDTAGANIMALS': {
+                'strict': False,
+                'labels': [
+                    {'label': 'Animal'},
+                    {'label': 'Creature'},
+                ],
+                'values': {
+                    'IDVALANIMAL1': {
+                        'labels': [
+                            {'label': 'Human'},
+                            {'label': 'Homo sapiens'},
+                        ],
+                    },
+                    'IDVALANIMAL2': {
+                        'labels': [
+                            {'label': 'Elephant'},
+                            {'label': 'Loxodonta'},
+                        ],
+                    },
+                },
+            },
+            'IDTAGIMPORTANCE': {
+                'strict': True,
+                'labels': [
+                    {'label': 'Importance'},
+                    {'label': 'Priority'},
+                ],
+                'values': {
+                    'IDVALIMPORTANCE1': {
+                        'labels': [
+                            {'label': 'High'},
+                            {'label': 'High priority'},
+                        ],
+                    },
+                    'IDVALIMPORTANCE2': {
+                        'labels': [
+                            {'label': 'Medium'},
+                            {'label': 'Medium priority'},
+                        ],
+                    },
+                    'IDVALIMPORTANCE3': {
+                        'labels': [
+                            {'label': 'Low'},
+                            {'label': 'Low priority'},
+                        ],
+                    },
+                },
+            },
+        },
+    }
+
+    def perform_vocabulary_tests(self, voc):
+        self.assertEqual(voc.strict_keys, False)
+        self.assertEqual(voc.key_aliases.keys(), set(['IDTAGANIMALS', 'IDTAGIMPORTANCE']))
+
+        self.assertEqual(voc.key_aliases['IDTAGANIMALS'].strict, False)
+        self.assertEqual(set(voc.key_aliases['IDTAGANIMALS'].aliases), set(['animal', 'creature']))
+        self.assertEqual(voc.key_aliases['IDTAGANIMALS'].values.keys(), set(['IDVALANIMAL1', 'IDVALANIMAL2']))
+        self.assertEqual(voc.key_aliases['IDTAGANIMALS'].values['IDVALANIMAL1'].aliases, set(['human', 'homo sapiens']))
+
+    def test_empty_vocabulary(self):
+        voc = vocabulary.Vocabulary()
+        self.assertEqual(voc.strict_keys, False)
+        self.assertEqual(voc.key_aliases, {})
+
+    def test_load_vocabulary(self):
+        voc = vocabulary.Vocabulary(self.EXAMPLE_VOC)
+        self.perform_vocabulary_tests(voc)
+
+    @mock.patch('arvados.api')
+    def test_load_vocabulary_with_api(self, api_mock):
+        api_mock.return_value = mock.MagicMock()
+        api_mock.return_value.vocabulary.return_value = self.EXAMPLE_VOC
+
+        voc = vocabulary.load_vocabulary(arvados.api('v1'))
+        self.perform_vocabulary_tests(voc)

commit 724c78fdbb4b489d1194ea8ded812dd8429a1c7d
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Feb 23 15:40:12 2022 -0300

    18574: Adds vocabulary endpoint section to the API documentation.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/doc/api/index.html.textile.liquid b/doc/api/index.html.textile.liquid
index 8586a166d..3d69d02ea 100644
--- a/doc/api/index.html.textile.liquid
+++ b/doc/api/index.html.textile.liquid
@@ -20,6 +20,10 @@ h2. Exported configuration
 
 The Controller exposes a subset of the cluster's configuration and makes it available to clients in JSON format. This public config includes valuable information like several service's URLs, timeout settings, etc. and it is available at @/arvados/v1/config@, for example @https://{{ site.arvados_api_host }}/arvados/v1/config at . The new Workbench is one example of a client using this information, as it's a client-side application and doesn't have access to the cluster's config file.
 
+h2. Exported vocabulary definition
+
+When configured, the Controller also exports the "metadata vocabulary definition":{{site.baseurl}}/admin/metadata-vocabulary.html in JSON format. This functionality is useful for clients like Workbench2 and the Python SDK to provide "identifier to human-readable labels" translations facilities for reading and writing objects on the system. This is available at @/arvados/v1/vocabulary@, for example @https://{{ site.arvados_api_host }}/arvados/v1/vocabulary at .
+
 h2. Workbench examples
 
 Many Arvados Workbench pages, under the *Advanced* tab, provide examples of API and SDK use for accessing the current resource .

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list