Repository navigation
[#835] allow key-agnostic iRODSMeta construction for AVU assignments #836
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f4dec6b
60889bb
4367d28
457df1a
642a175
b320849
c68c9ca
415e7f5
af75c13
ac5de9e
5d8c0da
3fefcf0
d61ab5d
017e031
21d2f4f
adc3470
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -836,22 +836,60 @@ 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()) | ||||||
| [<iRODSMeta 13182 key1 value1 units1>, <iRODSMeta 13183 key1 value2 None>, | ||||||
| <iRODSMeta 13186 key2 value5 units2>] | ||||||
| ``` | ||||||
|
|
||||||
| With only one AVU on the object with a name of "key2", *get_one* | ||||||
| is assured of not throwing an exception: | ||||||
| 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') | ||||||
| ``` | ||||||
|
Comment on lines
+849
to
+851
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing python styling keyword on opening backticks. |
||||||
|
|
||||||
| 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 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| with code such as | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add colon after "as"? Noticed other places in this diff had a colon.
Suggested change
|
||||||
|
|
||||||
| ``` | ||||||
| x = obj.metadata['key1'] | ||||||
| ``` | ||||||
|
Comment on lines
+860
to
+862
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing python styling keyword on opening backticks. |
||||||
|
|
||||||
| 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 | ||||||
| rather than reliable, straightforward coding practice. For clear and unambiguous intent, the | ||||||
| canonical iRODS API endpoints should be used, with calls such as | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add colon after "as"? Noticed other places in this diff had a colon.
Suggested change
|
||||||
|
|
||||||
| ```python | ||||||
| obj.metadata.set('mykey1','myvalue1') | ||||||
| obj.metadata.set('mykey2','myvalue2','myunits2') | ||||||
| obj.metadata.set(*iRODSMeta('mykey3','myvalue3')) | ||||||
| ``` | ||||||
|
|
||||||
| being preferred. (All of the 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 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')) | ||||||
| <iRODSMeta 13186 key2 value5 units2> | ||||||
| ``` | ||||||
|
|
||||||
| However, the same is not true of "key1": | ||||||
| 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')) | ||||||
|
|
@@ -862,6 +900,9 @@ Traceback (most recent call last): | |||||
| KeyError | ||||||
| ``` | ||||||
|
|
||||||
| Metadata removal and clean disposal | ||||||
| ----------------------------------- | ||||||
|
|
||||||
| Finally, to remove a specific AVU from an object: | ||||||
|
|
||||||
| ```python | ||||||
|
|
@@ -922,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 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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() | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,11 @@ | ||
| #! /usr/bin/env python | ||
| # -*- coding: utf-8 -*- | ||
|
|
||
| import collections | ||
| import datetime | ||
| import os | ||
| import re | ||
| import string | ||
| import sys | ||
| import time | ||
| import unittest | ||
|
|
@@ -798,6 +800,72 @@ | |
| # 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): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this test about exercising basic functionality of the new builder? |
||
| 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 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)) | ||
|
d-w-moore marked this conversation as resolved.
|
||
| avu_names = [_ for _ in string.ascii_lowercase] | ||
| 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)) | ||
|
|
||
| # Define constants. | ||
| KM_PER_MILE = '1.609344' | ||
| CM_PER_FOOT = '30.48' | ||
|
|
||
| # 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( | ||
|
d-w-moore marked this conversation as resolved.
|
||
| { | ||
| iRODSMeta('mile', KM_PER_MILE, 'km'), | ||
| iRODSMeta('foot', CM_PER_FOOT, 'cm'), | ||
| }, | ||
| set(data_obj.metadata.items()) | ||
| ) | ||
|
Comment on lines
+831
to
+841
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm understanding correctly, |
||
| finally: | ||
| # Delete the test object. | ||
| if data_obj: | ||
| data_obj.unlink(force=True) | ||
|
|
||
| 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) | ||
|
|
||
| # 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) | ||
|
|
||
| # Assign a new AVU. | ||
| dm['test_key'] = iRODSMeta.builder(**( | ||
| avu_without_name := collections.OrderedDict(value=b'\3', units=b'd\0ef') | ||
| )) | ||
|
|
||
| # 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) | ||
|
|
||
| if __name__ == "__main__": | ||
| # let the tests find the parent irods lib | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to determine if there's a pattern in how we style types and other important words. I suspect there are inconsistencies in the README.
We need to do a consistency sweep across the READMEs. That can happen in a separate PR.