From f4dec6b16dc65c7f3bef549cd9ac7b7b5c669c9f Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Mon, 14 Sep 2026 00:50:09 -0400 Subject: [PATCH 01/15] [_835] allow key-agnostic iRODSMeta construction for __setitem__ AVU assignments --- irods/meta.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/irods/meta.py b/irods/meta.py index 5829e68d1..fbb89e0b3 100644 --- a/irods/meta.py +++ b/irods/meta.py @@ -1,8 +1,20 @@ import base64 +import collections import copy +import functools +_AVU_builder = functools.partial( + _AVU_type:=collections.namedtuple( + '_AVU_type', + ['name','value','units'] + ), + name=None, units=None +) class iRODSMeta: + + builder = staticmethod(_AVU_builder) + def _to_column_triple(self): return (self.name, self.forward_translate(self.value)) + ( ('',) if not self.units else (self.forward_translate(self.units),) @@ -296,6 +308,10 @@ def __setitem__(self, key, meta): the key with a single iRODSMeta tuple """ self._delete_all_values(key) + if isinstance(meta, _AVU_type): + meta = iRODSMeta(*meta) + if meta.name is None: + meta.name = key self.add(meta) def _delete_all_values(self, key): From 60889bb15656543e0ca8b0f4febd099cb7827eea Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Mon, 14 Sep 2026 12:24:53 -0400 Subject: [PATCH 02/15] [_835] test --- irods/test/meta_test.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index 880bf1fe5..5fd220fb3 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -798,6 +798,24 @@ def test_prevention_of_attribute_creation__issue_795(self): # data.metadata(admin = True) generates a cloned object but for the one change to "admin". data.metadata.admin = True + def test_iRODSMeta_builder__issue_835(self): + data_path = iRODSPath(self.coll_path, helpers.unique_name(datetime.datetime.now())) # noqa: DTZ005 + data = None + try: + data = self.sess.data_objects.create(data_path) + for x in map(chr,myrange:=range(ord('a'),ord('z')+1)): + d.metadata[x] = iRODSMeta.builder(value = str(ord(x))) + self.assertEqual(len(myitems:=d.metadata.items()), len(myrange)) + for x in myitems: + self.assertEqual(ord(x.value), x.key) + d.metadata['mile'] = iRODSMeta.builder(value = '1.609', units='kilometers') + self.assertIn( + iRODSMeta(name = 'mile', value = '1.609', units='kilometers') + d.metadata.items() + ) + finally: + if data: + data.unlink(force=True) if __name__ == "__main__": # let the tests find the parent irods lib From 4367d28f790801dc017d18a9d8068352a062ea17 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Mon, 14 Sep 2026 14:11:39 -0400 Subject: [PATCH 03/15] corrections to the test --- irods/test/meta_test.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index 5fd220fb3..c8f2bf449 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -800,22 +800,22 @@ def test_prevention_of_attribute_creation__issue_795(self): def test_iRODSMeta_builder__issue_835(self): data_path = iRODSPath(self.coll_path, helpers.unique_name(datetime.datetime.now())) # noqa: DTZ005 - data = None + data_obj = None try: - data = self.sess.data_objects.create(data_path) + data_obj = self.sess.data_objects.create(data_path) for x in map(chr,myrange:=range(ord('a'),ord('z')+1)): - d.metadata[x] = iRODSMeta.builder(value = str(ord(x))) - self.assertEqual(len(myitems:=d.metadata.items()), len(myrange)) - for x in myitems: - self.assertEqual(ord(x.value), x.key) - d.metadata['mile'] = iRODSMeta.builder(value = '1.609', units='kilometers') + data_obj.metadata[x] = iRODSMeta.builder(value = str(ord(x))) + self.assertEqual(len(myitems:=data_obj.metadata.items()), len(myrange)) + for avu in myitems: + self.assertEqual(chr(int(avu.value)), avu.name) + data_obj.metadata['mile'] = iRODSMeta.builder(value = '1.609', units='kilometers') self.assertIn( - iRODSMeta(name = 'mile', value = '1.609', units='kilometers') - d.metadata.items() + iRODSMeta('mile', '1.609', units='kilometers'), + data_obj.metadata.items() ) finally: - if data: - data.unlink(force=True) + if data_obj: + data_obj.unlink(force=True) if __name__ == "__main__": # let the tests find the parent irods lib From 457df1aaa91f297f417212634b3d2c26fcb5eda5 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Mon, 14 Sep 2026 18:51:07 -0400 Subject: [PATCH 04/15] README section on iRODSMeta.builder --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d136657b6..c5d446c9f 100644 --- a/README.md +++ b/README.md @@ -836,22 +836,31 @@ of an "imeta set \...", e.g. overwriting all AVUs with a name field of "key2" in a single update: ```python ->>> new_meta = iRODSMeta('key2','value5','units2') ->>> obj.metadata\[new_meta.name\] = new_meta +>>> obj.metadata['key2'] = iRODSMeta('key2','value5','units2') >>> print(obj.metadata.items()) [, , ] ``` -With only one AVU on the object with a name of "key2", *get_one* -is assured of not throwing an exception: +Alternatively, rather than the direct call to the iRODSMeta constructor as +shown above, there is also this slightly different approach to setting the +same AVU -- at the same time avoiding repeat use of the key string on both +left- and right-hand sides of the assignment: + +``` +>>> obj.metadata['key2'] = iRODSMeta.builder(value='value5', units='units2') +``` + +*get_one()* is a way of retrieving an AVU by its name field if we want to +assert that exactly one such AVU should exist; fewer or more than 1 raises +a KeyError. Here, it can be used to retrieve the "key2" AVU: ```python >>> print(obj.metadata.get_one('key2')) ``` -However, the same is not true of "key1": +But the same is not true for "key1": ```python >>> print(obj.metadata.get_one('key1')) From 642a1754b058be94bf09fcf1de7a18b7b2fa2182 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 07:17:42 -0400 Subject: [PATCH 05/15] avubuilder chgs and further mods for iRODSMeta subclass compatibility --- irods/meta.py | 19 ++++++++++++++----- irods/test/meta_test.py | 10 ++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/irods/meta.py b/irods/meta.py index fbb89e0b3..c3c7ae14b 100644 --- a/irods/meta.py +++ b/irods/meta.py @@ -3,6 +3,14 @@ import copy import functools +class avubuilder: + def __init__(self, value, units=None, *, name=None, _class=None): + self._class = (_class if _class is not None else iRODSMeta) + self.avu_builder = _AVU_builder(name=name, value=value, units=units) + + def __call__(self): + return self._class(*self.avu_builder) + _AVU_builder = functools.partial( _AVU_type:=collections.namedtuple( '_AVU_type', @@ -13,7 +21,7 @@ class iRODSMeta: - builder = staticmethod(_AVU_builder) + builder = avubuilder def _to_column_triple(self): return (self.name, self.forward_translate(self.value)) + ( @@ -247,7 +255,8 @@ def get_one(self, key): def _get_meta(self, *args): if not len(args): raise ValueError("Must specify an iRODSMeta object or key, value, units)") - return args[0] if len(args) == 1 else self._manager._opts['iRODSMeta_type'](*args) + #return args[0] if len(args) == 1 else self._manager._opts['iRODSMeta_type'](*args) + return self._manager._opts['iRODSMeta_type'](*(args[0] if len(args)==1 else args)) def apply_atomic_operations(self, *avu_ops): self._manager.apply_atomic_operations(self._model_cls, self._path, *avu_ops) @@ -308,11 +317,11 @@ def __setitem__(self, key, meta): the key with a single iRODSMeta tuple """ self._delete_all_values(key) - if isinstance(meta, _AVU_type): - meta = iRODSMeta(*meta) + if isinstance(meta, iRODSMeta.builder): + meta = meta() if meta.name is None: meta.name = key - self.add(meta) + self.add(*meta) def _delete_all_values(self, key): for meta in self.get_all(key): diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index c8f2bf449..3fe027c26 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -817,6 +817,16 @@ def test_iRODSMeta_builder__issue_835(self): if data_obj: data_obj.unlink(force=True) + def test_iRODSMeta_subclass_assign__issue_835(self): + pass + # from irods.meta import iRODSMeta, iRODSBinOrStringMeta + # import irods + # s=irods.helpers.make_session() + # d=s.data_objects.get('/tempZone/home/rods/b') + # d.metadata['a']=iRODSMeta.builder(value='C',units='D') + # bb=iRODSMeta.builder(b'ccaa','d',_class=iRODSBinOrStringMeta) + # d.metadata['laaaa'] = bb + if __name__ == "__main__": # let the tests find the parent irods lib sys.path.insert(0, os.path.abspath("../..")) From b32084916594d5a88915a0e65e1b242e55645aad Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 08:26:28 -0400 Subject: [PATCH 06/15] test example --- irods/test/meta_test.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index 3fe027c26..87629913a 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -823,9 +823,18 @@ def test_iRODSMeta_subclass_assign__issue_835(self): # import irods # s=irods.helpers.make_session() # d=s.data_objects.get('/tempZone/home/rods/b') - # d.metadata['a']=iRODSMeta.builder(value='C',units='D') - # bb=iRODSMeta.builder(b'ccaa','d',_class=iRODSBinOrStringMeta) - # d.metadata['laaaa'] = bb + # dm=d.metadata(iRODSMeta_type=iRODSBinOrStringMeta) + # #dm.set('a5',b'abc',b'def') + # dm.add('a5',b'2abc',b'def') + # dm['a6']=iRODSMeta.builder(value = b'abc', units = b'def') + # dm.add(*iRODSMeta('a7','b','c')) + # #dm['lad'] = bb + # #bb=iRODSMeta.builder(b'ccaa',b'd',) + # print( + # f"{dm['a5']=}", + # f"{dm['a6']=}", + # f"{dm['a7']=}" + # ) if __name__ == "__main__": # let the tests find the parent irods lib From c68c9cad2d994fd9cd53df1446ebb54a1bd9b62f Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 08:04:23 -0400 Subject: [PATCH 07/15] README refinements --- README.md | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c5d446c9f..b53175e43 100644 --- a/README.md +++ b/README.md @@ -842,25 +842,54 @@ of "key2" in a single update: ] ``` -Alternatively, rather than the direct call to the iRODSMeta constructor as -shown above, there is also this slightly different approach to setting the -same AVU -- at the same time avoiding repeat use of the key string on both -left- and right-hand sides of the assignment: +Alternatively, in the indexed AVU assignment, the following construction can +be used in lieu of the direct call to the iRODSMeta constructor, thus avoiding redundant use of the +key string on both left- and right-hand sides of the assignment: ``` >>> obj.metadata['key2'] = iRODSMeta.builder(value='value5', units='units2') ``` +Lest there should be a misunderstanding, this statement as given will clear +any other pre-existing AVUs with a name field of 'key2' before the requested assignment is +actually made. + +Be aware that use of the indexing form on obj.metadata to retrieve AVUs +with code such as the following: + +``` +x = obj.metadata['key1'] +``` + +can also act in ways unexpected by the unwary developer. If multiple AVUs +exist with the given name field, one will be chosen and returned at random. + +For this reason, the indexing form is better considered an artifact of convenience +rather than reliable and straightforward coding practice. For unambiguous intent, the +canonical iRODS API endpoints should be preferred, namely with calls such as: + +```python +obj.metadata.set('mykey1','myvalue1') +obj.metadata.set('mykey2','myvalue2','myunits2') +obj.metadata.set(*iRODSMeta('mykey3','myvalue3')) +``` + +(and note that the exact same usages apply for the `add` API endpoint as well.) + +Enforcing a "singleton" AVU +--------------------------- *get_one()* is a way of retrieving an AVU by its name field if we want to -assert that exactly one such AVU should exist; fewer or more than 1 raises -a KeyError. Here, it can be used to retrieve the "key2" AVU: +assert that exactly one such AVU should exist (fewer or more than 1 will raise +a `KeyError`). Here, it can be used to retrieve the "key2" AVU (since the indexed +assignment from the last section has removed all but the one): ```python >>> print(obj.metadata.get_one('key2')) ``` -But the same is not true for "key1": +But for our present example the same is not true in the case of "key1", since +we've left several AVUs under that name. ```python >>> print(obj.metadata.get_one('key1')) From 415e7f51e1b258bf6d1dd892f315cbfebf2dd9d6 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 08:28:47 -0400 Subject: [PATCH 08/15] other chgs to meta_test --- irods/test/meta_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index 87629913a..d207e14fa 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -817,8 +817,8 @@ def test_iRODSMeta_builder__issue_835(self): if data_obj: data_obj.unlink(force=True) - def test_iRODSMeta_subclass_assign__issue_835(self): - pass + #def test_iRODSMeta_subclass_assign__issue_835(self): + # pass # from irods.meta import iRODSMeta, iRODSBinOrStringMeta # import irods # s=irods.helpers.make_session() From af75c1350b42898145199594f3990d1955e255b1 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 08:24:21 -0400 Subject: [PATCH 09/15] [_new_issue] touch and improve README --- README.md | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b53175e43..270c94a99 100644 --- a/README.md +++ b/README.md @@ -850,23 +850,23 @@ key string on both left- and right-hand sides of the assignment: >>> obj.metadata['key2'] = iRODSMeta.builder(value='value5', units='units2') ``` -Lest there should be a misunderstanding, this statement as given will clear +Lest there should be a misunderstanding, this form of assignment will also clear any other pre-existing AVUs with a name field of 'key2' before the requested assignment is actually made. Be aware that use of the indexing form on obj.metadata to retrieve AVUs -with code such as the following: +with code such as ``` x = obj.metadata['key1'] ``` -can also act in ways unexpected by the unwary developer. If multiple AVUs -exist with the given name field, one will be chosen and returned at random. +can also act in ways unexpected by the unwary developer. If multiple AVUs +exist under the given name field, one will be chosen and returned at random. -For this reason, the indexing form is better considered an artifact of convenience -rather than reliable and straightforward coding practice. For unambiguous intent, the -canonical iRODS API endpoints should be preferred, namely with calls such as: +For these reasons, the indexed assignment may be better considered an artifact of convenience +rather than reliable, straightforward coding practice. For clear and unambiguous intent, the +canonical iRODS API endpoints should be used, with calls such as ```python obj.metadata.set('mykey1','myvalue1') @@ -874,10 +874,10 @@ obj.metadata.set('mykey2','myvalue2','myunits2') obj.metadata.set(*iRODSMeta('mykey3','myvalue3')) ``` -(and note that the exact same usages apply for the `add` API endpoint as well.) +being preferred. (All of the same usages apply for the `add` API endpoint as well.) -Enforcing a "singleton" AVU ---------------------------- +Enforcing a singleton AVU +------------------------- *get_one()* is a way of retrieving an AVU by its name field if we want to assert that exactly one such AVU should exist (fewer or more than 1 will raise a `KeyError`). Here, it can be used to retrieve the "key2" AVU (since the indexed @@ -888,8 +888,8 @@ assignment from the last section has removed all but the one): ``` -But for our present example the same is not true in the case of "key1", since -we've left several AVUs under that name. +But for our present example, the same is not true in the case of "key1", since +we have now left several AVUs under that name. ```python >>> print(obj.metadata.get_one('key1')) @@ -900,6 +900,9 @@ Traceback (most recent call last): KeyError ``` +Metadata removal and clean disposal +----------------------------------- + Finally, to remove a specific AVU from an object: ```python @@ -960,20 +963,21 @@ Since v1.1.4, `set()` can be used instead: >>> album.metadata.set( meta ) ``` -In versions of iRODS 4.2.12 and later, we can also do: +In iRODS 4.2.12 and after, a rodsadmin can apply the ADMIN_KW +thus allowing modification of AVUs owned by other users: ```python ->>> album.metadata.set( meta, \*\*{kw.ADMIN_KW: ''} ) +>>> album.metadata.set(meta, **{kw.ADMIN_KW: ''}) ``` -or even: +Equivalently, but with increased overhead, this does the same thing: ```python ->>> album.metadata(admin = True)\[meta.name\] = meta +>>> album.metadata(admin=True)[meta.name] = meta ``` -Since v1.1.5, the "timestamps" keyword is provided to enable the loading -of create and modify timestamps for every AVU returned from the server: +A "timestamps" keyword is also provided to enable loading of the +`create_time` and `modify_time` attributes for every AVU returned from the server: ```python >>> avus = album.metadata(timestamps = True).items() From ac5de9e643d6c79ab7616f0d10412a07f287d1dc Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 09:35:11 -0400 Subject: [PATCH 10/15] tests/comments --- irods/test/meta_test.py | 54 ++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index d207e14fa..0cd4fb486 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -1,6 +1,7 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- +import collections import datetime import os import re @@ -803,38 +804,47 @@ def test_iRODSMeta_builder__issue_835(self): data_obj = None try: data_obj = self.sess.data_objects.create(data_path) + + # Set and assert a number of AVUs with the iRODSMeta builder invocation. for x in map(chr,myrange:=range(ord('a'),ord('z')+1)): data_obj.metadata[x] = iRODSMeta.builder(value = str(ord(x))) self.assertEqual(len(myitems:=data_obj.metadata.items()), len(myrange)) for avu in myitems: self.assertEqual(chr(int(avu.value)), avu.name) - data_obj.metadata['mile'] = iRODSMeta.builder(value = '1.609', units='kilometers') - self.assertIn( - iRODSMeta('mile', '1.609', units='kilometers'), - data_obj.metadata.items() + + # Use both forms of the iRODSMeta builder invocation + data_obj.metadata['mile'] = iRODSMeta.builder(value = (KM_PER_MILE:='1.609344'), units='km') + data_obj.metadata['foot'] = iRODSMeta.builder(CM_PER_FOOT:='30.48', 'cm') + + # Assert that AVUs were properly set, using < operator to mean "is a proper subset of". + self.assertLess( + { + iRODSMeta('mile', KM_PER_MILE, 'km'), + iRODSMeta('foot', CM_PER_FOOT, 'cm'), + }, + set(data_obj.metadata.items()) ) finally: if data_obj: data_obj.unlink(force=True) - #def test_iRODSMeta_subclass_assign__issue_835(self): - # pass - # from irods.meta import iRODSMeta, iRODSBinOrStringMeta - # import irods - # s=irods.helpers.make_session() - # d=s.data_objects.get('/tempZone/home/rods/b') - # dm=d.metadata(iRODSMeta_type=iRODSBinOrStringMeta) - # #dm.set('a5',b'abc',b'def') - # dm.add('a5',b'2abc',b'def') - # dm['a6']=iRODSMeta.builder(value = b'abc', units = b'def') - # dm.add(*iRODSMeta('a7','b','c')) - # #dm['lad'] = bb - # #bb=iRODSMeta.builder(b'ccaa',b'd',) - # print( - # f"{dm['a5']=}", - # f"{dm['a6']=}", - # f"{dm['a7']=}" - # ) + def test_indexed_assignments_are_iRODSMeta_subclass_compatible__issue_835(self): + data_path = iRODSPath(self.coll_path, helpers.unique_name(datetime.datetime.now())) # noqa: DTZ005 + data_obj = None + try: + data_obj = self.sess.data_objects.create(data_path) + d = self.sess.data_objects.get('/tempZone/home/rods/b') + + # Test iRODSMeta builder with custom iRODSMeta conversion subclass. + dm = d.metadata(iRODSMeta_type=iRODSBinOrStringMeta) + dm['myindex'] = iRODSMeta.builder(**( + avu_without_name := collections.OrderedDict(value=b'\3', units=b'd\0ef') + )) + test_avu = iRODSMeta('myindex',*list(avu_without_name.values())) + self.assertEqual(test_avu, dm['myindex']) + finally: + if data_obj: + data_obj.unlink(force=True) if __name__ == "__main__": # let the tests find the parent irods lib From 5d8c0dacccaf63b9df6879a66f39ee6cf610e319 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 09:42:10 -0400 Subject: [PATCH 11/15] no longer need custom class component --- irods/meta.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/irods/meta.py b/irods/meta.py index c3c7ae14b..407be0f35 100644 --- a/irods/meta.py +++ b/irods/meta.py @@ -4,12 +4,11 @@ import functools class avubuilder: - def __init__(self, value, units=None, *, name=None, _class=None): - self._class = (_class if _class is not None else iRODSMeta) + def __init__(self, value, units=None, *, name=None): self.avu_builder = _AVU_builder(name=name, value=value, units=units) def __call__(self): - return self._class(*self.avu_builder) + return iRODSMeta(*self.avu_builder) _AVU_builder = functools.partial( _AVU_type:=collections.namedtuple( From 3fefcf005e3ea782b07f81eadeadf9e0f5a2411b Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 09:45:46 -0400 Subject: [PATCH 12/15] whitespace --- README.md | 4 ++-- irods/meta.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 270c94a99..9f96e9f12 100644 --- a/README.md +++ b/README.md @@ -861,7 +861,7 @@ with code such as x = obj.metadata['key1'] ``` -can also act in ways unexpected by the unwary developer. If multiple AVUs +can also act in ways unexpected by the unwary developer. If multiple AVUs exist under the given name field, one will be chosen and returned at random. For these reasons, the indexed assignment may be better considered an artifact of convenience @@ -963,7 +963,7 @@ Since v1.1.4, `set()` can be used instead: >>> album.metadata.set( meta ) ``` -In iRODS 4.2.12 and after, a rodsadmin can apply the ADMIN_KW +In iRODS 4.2.12 and after, a rodsadmin can apply the ADMIN_KW thus allowing modification of AVUs owned by other users: ```python diff --git a/irods/meta.py b/irods/meta.py index 407be0f35..a06a85729 100644 --- a/irods/meta.py +++ b/irods/meta.py @@ -9,7 +9,7 @@ def __init__(self, value, units=None, *, name=None): def __call__(self): return iRODSMeta(*self.avu_builder) - + _AVU_builder = functools.partial( _AVU_type:=collections.namedtuple( '_AVU_type', From d61ab5dbf56701231c51de649a188777153ae586 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Tue, 15 Sep 2026 12:49:35 -0400 Subject: [PATCH 13/15] use temp dat obj in test --- irods/test/meta_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index 0cd4fb486..2308e076c 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -833,10 +833,9 @@ def test_indexed_assignments_are_iRODSMeta_subclass_compatible__issue_835(self): data_obj = None try: data_obj = self.sess.data_objects.create(data_path) - d = self.sess.data_objects.get('/tempZone/home/rods/b') # Test iRODSMeta builder with custom iRODSMeta conversion subclass. - dm = d.metadata(iRODSMeta_type=iRODSBinOrStringMeta) + dm = data_obj.metadata(iRODSMeta_type=iRODSBinOrStringMeta) dm['myindex'] = iRODSMeta.builder(**( avu_without_name := collections.OrderedDict(value=b'\3', units=b'd\0ef') )) From 017e0311c34c1024fce1336109a07fdbb61cbd87 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Wed, 16 Sep 2026 04:32:12 -0400 Subject: [PATCH 14/15] add more comments for explanation in tests --- irods/test/meta_test.py | 49 +++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/irods/test/meta_test.py b/irods/test/meta_test.py index 2308e076c..eff3a4ac7 100644 --- a/irods/test/meta_test.py +++ b/irods/test/meta_test.py @@ -803,20 +803,34 @@ def test_iRODSMeta_builder__issue_835(self): data_path = iRODSPath(self.coll_path, helpers.unique_name(datetime.datetime.now())) # noqa: DTZ005 data_obj = None try: + # Create a test object on which to set metadata. data_obj = self.sess.data_objects.create(data_path) - # Set and assert a number of AVUs with the iRODSMeta builder invocation. - for x in map(chr,myrange:=range(ord('a'),ord('z')+1)): - data_obj.metadata[x] = iRODSMeta.builder(value = str(ord(x))) - self.assertEqual(len(myitems:=data_obj.metadata.items()), len(myrange)) - for avu in myitems: - self.assertEqual(chr(int(avu.value)), avu.name) + # Set a number of metadata AVUs with the iRODSMeta builder invocation. (Each of + # these AVU values follow a predictable relation defined by the test_mapping function.) + def test_mapping(name): return str(ord(name)) + avu_names = [chr(_) for _ in range(ord('a'), ord('z')+1)] + for ch in avu_names: + data_obj.metadata[ch] = iRODSMeta.builder(value = test_mapping(ch)) + + # Assert there are as many AVUs as expected + self.assertEqual( + len(myitems := data_obj.metadata.items()), + len(avu_names) + ) + + # Assert that each AVU conforms to the expected name->value mapping. + for avu in myitems: + self.assertEqual(avu.value, test_mapping(avu.name)) - # Use both forms of the iRODSMeta builder invocation - data_obj.metadata['mile'] = iRODSMeta.builder(value = (KM_PER_MILE:='1.609344'), units='km') - data_obj.metadata['foot'] = iRODSMeta.builder(CM_PER_FOOT:='30.48', 'cm') + # Define constants. + KM_PER_MILE = '1.609344' + CM_PER_FOOT = '30.48' - # Assert that AVUs were properly set, using < operator to mean "is a proper subset of". + # Use both forms of the iRODSMeta builder invocation, testing that both attempts + # resulted in the AVU we expected. (For sets, s1 < s2 iff s1 is a proper subset of s2.) + data_obj.metadata['mile'] = iRODSMeta.builder(value=KM_PER_MILE, units='km') + data_obj.metadata['foot'] = iRODSMeta.builder(CM_PER_FOOT, 'cm') self.assertLess( { iRODSMeta('mile', KM_PER_MILE, 'km'), @@ -825,6 +839,7 @@ def test_iRODSMeta_builder__issue_835(self): set(data_obj.metadata.items()) ) finally: + # Delete the test object. if data_obj: data_obj.unlink(force=True) @@ -834,13 +849,19 @@ def test_indexed_assignments_are_iRODSMeta_subclass_compatible__issue_835(self): try: data_obj = self.sess.data_objects.create(data_path) - # Test iRODSMeta builder with custom iRODSMeta conversion subclass. + # Test use of the iRODSMeta builder with a custom iRODSMeta-derived getter/setter (which + # in this case automatically performs user defined conversions to and from byte strings). dm = data_obj.metadata(iRODSMeta_type=iRODSBinOrStringMeta) - dm['myindex'] = iRODSMeta.builder(**( + + # Assign a new AVU. + dm['test_key'] = iRODSMeta.builder(**( avu_without_name := collections.OrderedDict(value=b'\3', units=b'd\0ef') )) - test_avu = iRODSMeta('myindex',*list(avu_without_name.values())) - self.assertEqual(test_avu, dm['myindex']) + + # Test that AVU storage happened with supported by the proper conversions (within the client) + # to and from 'str' type required for the 'value' and 'units' fields of an AVU. + test_avu = iRODSMeta('test_key',*list(avu_without_name.values())) + self.assertEqual(test_avu, dm['test_key']) finally: if data_obj: data_obj.unlink(force=True) From 21d2f4ffc91dad18353d1df77334cf17939c486d Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Wed, 16 Sep 2026 04:34:18 -0400 Subject: [PATCH 15/15] delete old commented-out code --- irods/meta.py | 1 - 1 file changed, 1 deletion(-) diff --git a/irods/meta.py b/irods/meta.py index a06a85729..f87ffc1c7 100644 --- a/irods/meta.py +++ b/irods/meta.py @@ -254,7 +254,6 @@ def get_one(self, key): def _get_meta(self, *args): if not len(args): raise ValueError("Must specify an iRODSMeta object or key, value, units)") - #return args[0] if len(args) == 1 else self._manager._opts['iRODSMeta_type'](*args) return self._manager._opts['iRODSMeta_type'](*(args[0] if len(args)==1 else args)) def apply_atomic_operations(self, *avu_ops):