Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions irods/manager/metadata_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
def __init__(self, *_):
self._opts = _MetadataManager_opts_initializer.copy()
super().__init__(*_)
# For the iRODS-api keywords only (currently ADMIN_KW is the sole one used):
self.__kw = {}

@property
def use_timestamps(self):
return self._opts['timestamps']

__kw: Dict[str, Any] = {} # default (empty) keywords

def _updated_keywords(self, opts):
kw_ = self.__kw.copy()
kw_.update(opts)
Expand All @@ -52,18 +52,20 @@
return self.__kw.copy()

def __call__(self, **flags):
# Make a new shallow copy of the manager object, but update options from parameter list.
# Make a new shallow copy of the manager object, but duplicate options from parameter list as well as iRODS API
# flags (stored in the instance's private __kw member) to be applied in each call.
new_self = copy.copy(self)
new_self._opts = copy.copy(self._opts)
new_self.__kw = copy.copy(self.__kw)

Check failure on line 59 in irods/manager/metadata_manager.py

View workflow job for this annotation

GitHub Actions / ruff-lint / ruff-check

Ruff private-member-access

private-member-access: Private member accessed: `__kw` [check:private-member-access]

# Update the flags that do bookkeeping in the returned(new) manager object.
new_self._opts.update((key, val) for key, val in flags.items() if val is not None)

# Update the ADMIN_KW flag in the returned(new) object.
# For the new object, make ADMIN_KW flag or absence thereof reflect the admin option in _opts.
if new_self._opts.get('admin'):
self.__kw[kw.ADMIN_KW] = ""
new_self.__kw[kw.ADMIN_KW] = ""

Check failure on line 66 in irods/manager/metadata_manager.py

View workflow job for this annotation

GitHub Actions / ruff-lint / ruff-check

Ruff private-member-access

private-member-access: Private member accessed: `__kw` [check:private-member-access]
else:
self.__kw.pop(kw.ADMIN_KW, None)
new_self.__kw.pop(kw.ADMIN_KW, None)

Check failure on line 68 in irods/manager/metadata_manager.py

View workflow job for this annotation

GitHub Actions / ruff-lint / ruff-check

Ruff private-member-access

private-member-access: Private member accessed: `__kw` [check:private-member-access]

return new_self

Expand Down
53 changes: 53 additions & 0 deletions irods/test/meta_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,59 @@
# data.metadata(admin = True) generates a cloned object but for the one change to "admin".
data.metadata.admin = True

def test_admin_mode_and_keyword_exhibit_no_stickyness__issue_833(self):
# Create a rodsuser, and a session for that roduser.
adm = self.sess
user = d = None
try:
# Create a test user.
user = adm.users.create("bobby", "rodsuser")
user.modify("password", "bpass")

# This is a convenience function to (re)instantiate the test iRODSSessions:
def new_session():
return iRODSSession(
port=adm.port,
zone=adm.zone,
host=adm.host,
user=user.name,
password="bpass",
)

with new_session() as ses1:
d = ses1.data_objects.create(data_name:="/{adm.zone}/home/{user.name}/testfile".format(**locals()))
d.metadata(admin=True)

with new_session() as ses2:
# Repeat the fetch of the data object using the new session, so we are clean of old references.
d = ses2.data_objects.get(data_name)

# In this use of set(), we expect not to end up applying ADMIN_KW in the underlying API call.
# (Doing so as a rodsuser would raise INSUFFICIENT_PRIVILEGE_LEVEL and the test would fail.)
d.metadata.set('a','b')

# Check that the option flag for use of ADMIN_KW is not set.
self.assertFalse(d.metadata.admin)

# This function duplicates the way in which the client API endpoint calculates iRODS option keywords
# for the underlying API call:
get_call_keywords = lambda metacoll: metacoll._manager._updated_keywords((),)

Check failure on line 837 in irods/test/meta_test.py

View workflow job for this annotation

GitHub Actions / ruff-lint / ruff-check

Ruff private-member-access

private-member-access: Private member accessed: `_updated_keywords` [check:private-member-access]

Check failure on line 837 in irods/test/meta_test.py

View workflow job for this annotation

GitHub Actions / ruff-lint / ruff-check

Ruff private-member-access

private-member-access: Private member accessed: `_manager` [check:private-member-access]

Check failure on line 837 in irods/test/meta_test.py

View workflow job for this annotation

GitHub Actions / ruff-lint / ruff-check

Ruff lambda-assignment

lambda-assignment: Do not assign a `lambda` expression, use a `def` [check:lambda-assignment]
Comment on lines +835 to +837

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this saying it duplicates the key-value pairs stored in the manager?

I'm struggling to understand what this does?

@d-w-moore d-w-moore Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It emulates the internal calculation of api keywords given to the iRODS api, based on the input metacoll.
So for two different such objects:

  get_call_keywords(Data.metadata(admin=False)) -> {}

and

  get_call_keywords(Data.metadata(admin=True)) -> {**ADMIN_KW:''}

is what you would expect.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It emulates the internal calculation of api keywords given to the iRODS api, based on the input metacoll.

By "iRODS api", I take it you're referring to the PRC's interface and NOT the iRODS RPC interface, correct?


So, that lambda is using code that is private to the implementation to prove correctness?

Is there no way to do this without reaching behind the public API of the library?


# Applying admin=True should result in API flags containing ADMIN_KW among the lookup keys.
md_modified=d.metadata(admin=True)
self.assertIn(kw.ADMIN_KW, get_call_keywords(md_modified))

# The modified admin setting should be reflected when reading it back from the object's
# internal options # bookkeeping.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the trailing # bookkeeping a leftover?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no , the extra # was to be deleted if I was editing better. But I can leave the word out if it makes more sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's remove that word.

self.assertTrue(md_modified.admin)

# But the original (unmodified) source object should not reflect use of an ADMIN_KW.
self.assertNotIn(kw.ADMIN_KW, get_call_keywords(d.metadata)) # keyword updates not reflected in copied obj.

Check failure on line 848 in irods/test/meta_test.py

View workflow job for this annotation

GitHub Actions / ruff-lint / ruff-check

Ruff line-too-long

line-too-long: Line too long (123 > 120) [check:line-too-long]

Check failure on line 848 in irods/test/meta_test.py

View workflow job for this annotation

GitHub Actions / ruff-lint / ruff-format

Ruff format

Improper formatting
finally:
if d:
d.unlink(force=True)
if user:
user.remove()

if __name__ == "__main__":
# let the tests find the parent irods lib
Expand Down
Loading