From b903d62a264d14e19879bd814c37f26b8d2cdd84 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Thu, 22 Oct 2026 12:09:26 -0400 Subject: [PATCH 1/8] [_561] implement get_maximum_permissions_for_path functionality and test A query is done to determine the highest permission level the given user (or group) has on the target collection/data_object. A specific query will be used if the calling user has the availability, otherwise a reasonably efficient general query is used instead. --- irods/access.py | 1 - irods/manager/access_manager.py | 138 +++++++++++++++++++++++++++++++- irods/query.py | 8 ++ irods/test/access_test.py | 114 ++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 2 deletions(-) diff --git a/irods/access.py b/irods/access.py index 1ad6660a7..27e0f061f 100644 --- a/irods/access.py +++ b/irods/access.py @@ -20,7 +20,6 @@ "null", ) - class _Access_LookupMeta(type): @staticmethod def _codes(): diff --git a/irods/manager/access_manager.py b/irods/manager/access_manager.py index 5873c1e15..25743950e 100644 --- a/irods/manager/access_manager.py +++ b/irods/manager/access_manager.py @@ -2,12 +2,14 @@ import logging from os.path import basename, dirname +import typing from irods.access import iRODSAccess from irods.api_number import api_number from irods.collection import iRODSCollection from irods.column import In from irods.data_object import irods_basename, irods_dirname, iRODSDataObject +from irods.exception import CAT_NO_ROWS_FOUND from irods.manager import Manager from irods.message import JSON_Message, ModAclRequest, iRODSMessage from irods.models import ( @@ -16,9 +18,12 @@ CollectionUser, DataAccess, DataObject, + Group, User, ) -from irods.user import iRODSUser +from irods.query import SpecificQuery +from irods.user import iRODSUser, iRODSGroup + logger = logging.getLogger(__name__) @@ -35,7 +40,138 @@ def users_by_ids(session, ids=()): return [iRODSUser(session.users, i) for i in session.query(User.id, User.name, User.type, User.zone).filter(*cond)] +class QueryParameterError(ValueError): + pass + +# This is the alias where we expect to find a specific query +alias_for_get_maximum_permission_for_path = 'get_maximum_permission_for_path' + +class _iRODSPermission(typing.NamedTuple): + as_string: str + as_integer: int + class AccessManager(Manager): + def get_maximum_permission_for_path(self, + user : [iRODSUser|iRODSGroup], + coll : [str|iRODSCollection|None]=None, + data : [str|iRODSDataObject|None]=None, + specific_query : [SpecificQuery|str|None]='', + ) -> int|str: + """ + Return the highest permission the named user/group has with respect to the + given data object or collection. In calculating this result, all ACLs and + group memberships are factored in. + + Only one of 'data' or 'coll' may be specified. + + Args: + user: an iRODSUser or iRODSGroup object for whom the permission is to be calculated. + coll: an iRODS logical path or iRODSCollection. + data: an iRODS logical path or iRODSDataObject. + specific_query (default ""): a SpecificQuery object or an alias specifying + the specific query used for the calculation. The default value of "" will be + interpreted to mean the designated specific query preconfigured for this express + purpose. If the server does not cache a query under that alias, or if None is + given as a value, then the calculation will be done via a general query. + + Returns: + a named tuple representing the permission results in terms of both the symbolic + string (.as_string) and integer code (.as_integer). If no ACLs + exist granting permission for the named user or group, then None is returned. + """ + ses = self.sess + + # Convert user or group to object. Need to know if we are dealing with a rodsadmin + if isinstance(user, (iRODSUser, iRODSGroup)): + pass + else: + msg = "'user' argument should be of type iRODSUser or iRODSGroup." + raise QueryParameterError(msg) + + _specific_query = None + + if specific_query == '': + specific_query = alias_for_get_maximum_permission_for_path + + if isinstance(specific_query, SpecificQuery): + _specific_query = specific_query + elif isinstance(specific_query, str): + _specific_query = SpecificQuery(ses, alias=specific_query) + logger=logging.getLogger('issue_561_query_type_detection') + # Dispatch to the available/chosen query method. + if _specific_query is not None: + logger.debug('SPECIFIC') + def denullify(s): + return s if s else "x" + _specific_query.reset_for_new_query( + args=[ + user.name, + getattr(user,'zone',ses.zone), + denullify(coll.path if isinstance(coll, iRODSCollection) else coll), + denullify(data.path if isinstance(data, iRODSDataObject) else data), + ] + ) + max_permission = self._get_maximum_permission_for_path_via_SpecificQuery(_specific_query) + res = [max_permission] + else: + logger.debug('GENERAL') + res = None + groups = {g[Group.id] for g in ses.query(Group, User).filter(Group.id != user.id, User.id == user.id)} + groups |= {user.id} + + if data: + if isinstance(data, str): + collN = irods_dirname(data) + dataN = irods_basename(data) + join_criteria = [DataObject.name == dataN, Collection.name == collN] + elif isinstance(data, iRODSDataObject): + join_criteria = [ DataAccess.data_id == data.id] + else: + msg="'data' parameter must be full logical path or data object" + raise ValueError(msg) + + q = ses.query(DataAccess.type).filter( + In(DataAccess.user_id, groups), + *join_criteria + ).order_by(DataAccess.type,order='desc') + + res = [_[DataAccess.type] for _ in q] + + elif coll: + if isinstance(coll, str): + join_criteria = [Collection.name == coll] + elif isinstance(coll, iRODSCollection): + join_criteria = [CollectionAccess.access_id == coll.id] # TODO: access_id is not an accurate name, should be coll_id + else: + msg="'coll' parameter must be full logical path or collection object" + raise ValueError(msg) + + q=ses.query(CollectionAccess).filter( + In(CollectionAccess.user_id, groups), + *join_criteria + ).order_by(CollectionAccess.type,order='desc') + + res = [_[CollectionAccess.type] for _ in q] + + else: + msg = 'Need a collection or data object' + raise QueryParameterError(msg) + + res = res[0] if res else None + return _iRODSPermission(iRODSAccess.strings[res], as_integer=res) if isinstance(res, int) else None + + @staticmethod + def _get_maximum_permission_for_path_via_SpecificQuery(spec_query): + try: + row = next(iter( + spec_query.execute() + )) + except (StopIteration, CAT_NO_ROWS_FOUND): + row = {} + + highest_perm = row.get(0) + return None if highest_perm is None else int(highest_perm) + @staticmethod def _to_acl_operation_json(op_input: iRODSAccess): return { diff --git a/irods/query.py b/irods/query.py index aa6e7a514..cce5e92e4 100644 --- a/irods/query.py +++ b/irods/query.py @@ -1,4 +1,5 @@ from collections import OrderedDict +import uuid from irods import MAX_SQL_ROWS from irods.models import Model @@ -290,6 +291,11 @@ def __init__(self, sess, sql=None, alias=None, columns=None, args=None): self._columns = columns self._args = args or [] + def reset_for_new_query(self, args=None): + self._continue_index = 0 + if args is not None: + self._args = args + def register(self): if not self._sql: raise ValueError("Empty query") @@ -363,6 +369,8 @@ def get_results(self): yield result + + # Record a copy of the original value of IRODS_QUERY_LIMIT for possible future access. import irods.helpers as helpers diff --git a/irods/test/access_test.py b/irods/test/access_test.py index 142bb2f5c..458293f83 100644 --- a/irods/test/access_test.py +++ b/irods/test/access_test.py @@ -1,9 +1,12 @@ #! /usr/bin/env python +import logging import os import sys +import textwrap import unittest +import irods.access from irods.access import ACLOperation, iRODSAccess from irods.collection import iRODSCollection from irods.column import In, Like @@ -14,6 +17,8 @@ from irods.test import helpers from irods.user import iRODSUser +from irods.manager.access_manager import alias_for_get_maximum_permission_for_path + class TestAccess(unittest.TestCase): def setUp(self): @@ -532,6 +537,115 @@ def test_atomic_acls__issue_505(self): if zone: zone.remove() + def test_get_maximum_permission_for_path__issue_561(self): + SQL = textwrap.dedent(''' + WITH target_user AS ( + SELECT user_id + FROM r_user_main + WHERE user_name = ? + AND zone_name = ? + ), + resolved_object AS ( + -- The path names a collection + SELECT coll_id AS object_id + FROM r_coll_main + WHERE coll_name = ? + + UNION + + -- The path names a data object: collection path + '/' + data name + SELECT d.data_id AS object_id + FROM r_data_main AS d + JOIN r_coll_main AS c ON c.coll_id = d.coll_id + WHERE CONCAT(c.coll_name, '/', d.data_name) = ? + ), + grants AS ( + -- Direct grants + SELECT a.access_type_id + FROM r_objt_access AS a + JOIN target_user AS u ON u.user_id = a.user_id + JOIN resolved_object AS o ON o.object_id = a.object_id + + UNION ALL + + -- Grants to groups the user belongs to + SELECT a.access_type_id + FROM r_user_group AS ug + JOIN target_user AS u ON u.user_id = ug.user_id + JOIN r_objt_access AS a ON a.user_id = ug.group_user_id + JOIN resolved_object AS o ON o.object_id = a.object_id + ) + SELECT access_type_id + FROM grants + ORDER BY access_type_id DESC + LIMIT 1;''' + ) + user = group = coll = None + ses = self.sess + my_specific_query=None + try: + coll = ses.collections.create('/tempZone/home/public/folder_561') + data = ses.data_objects.create('/tempZone/home/public/folder_561/file') + user = ses.users.create('bob','rodsuser') + group = ses.groups.create('lab') + + # Set up test scenario with a new user and group, the group having deliberately higher permission + # so as to be able to account for the effect of group membership in the query results. + USER_PERM = 'modify_metadata' + GROUP_PERM = 'modify_object' + test_perms = { user.name: USER_PERM, group.name: GROUP_PERM } + for obj in (coll, data): + ses.acls.set(iRODSAccess(USER_PERM, obj.path, user.name)) + ses.acls.set(iRODSAccess(GROUP_PERM, obj.path, group.name)) + + try: + SpecificQuery(ses, alias='findQueryByAlias', args=(alias_for_get_maximum_permission_for_path,)).execute() + except CAT_NO_ROWS_FOUND: + query_alias = str(uuid.uuid1()), + my_specific_query = SpecificQuery(ses, sql=SQL, alias=query_alias) + + logger=logging.getLogger('issue_561_query_type_detection') + for is_member in (False, True): + if is_member: + group.addmember(user.name,user.zone) + for usr in (user, group): + max_user = usr if usr not in group.members else group + expected_permission = test_perms[max_user.name] + for obj_type, obj in ( + ("coll", coll), + ("coll", coll.path), + ("data", data), + ("data", data.path), + ): + query_options = { + 'GENERAL':{'specific_query':None}, + 'SPECIFIC':({} if not my_specific_query else {'specific_query':my_specific_query}) + } + for query_index in ('SPECIFIC', 'GENERAL'): + with self.subTest(f"Issue 561 subTest: {is_member=} {usr=} {obj=} {query_index=}"): + try: + logger.addHandler(handler:=StreamHandler(stream:=io.StringIO())) + permission = ses.acls.get_maximum_permission_for_path( + usr, + **query_options[query_index], + **{obj_type: obj}, + ).as_string + self.assertEqual(permission, expected_permission) + # Check logging output to verify which code path was taken. + self.assertEqual(stream.getvalue(), query_index) + finally: + logger.removeHandler(handler) + finally: + if coll: + # Removes test data object as well. + coll.remove(force=True, recurse=True) + if user: + user.remove() + if group: + group.remove() + if specific_query: + specific_query.remove() + if __name__ == "__main__": # let the tests find the parent irods lib From 9a623c804a5a5c7ebd61132d142aa51613584806 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Sun, 1 Nov 2026 08:15:00 -0500 Subject: [PATCH 2/8] [_843] iRODSGroup - proper zone handling --- irods/manager/user_manager.py | 8 ++++++++ irods/user.py | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/irods/manager/user_manager.py b/irods/manager/user_manager.py index 50b35ecae..dcfde9d4a 100644 --- a/irods/manager/user_manager.py +++ b/irods/manager/user_manager.py @@ -16,6 +16,7 @@ UserDoesNotExist, GroupDoesNotExist, NoResultFound, + MultipleResultsFound, CAT_SQL_ERR, ) from irods.api_number import api_number @@ -281,12 +282,19 @@ def get__group_create__user_type__default(session): class GroupManager(UserManager): def get(self, name, user_zone=""): + if user_zone not in ("", local_zone:=self.sess.zone): + msg = 'Specified user_zone must be local, since remote groups are not listed locally.' + raise GroupDoesNotExist(msg) + query = self.sess.query(Group).filter(Group.name == name) try: result = query.one() + except MultipleResultsFound: + result = query.filter(User.name == name, User.zone == local_zone).one() except NoResultFound: raise GroupDoesNotExist() + return iRODSGroup(self, result) def _api_info(self, group_admin_flag): diff --git a/irods/user.py b/irods/user.py index 4312eef64..453f0ed0b 100644 --- a/irods/user.py +++ b/irods/user.py @@ -82,6 +82,13 @@ def remove_quota(self, resource="total"): def set_quota(self, amount, resource="total"): self.manager.set_quota(self.name, amount, resource=resource) + @property + def zone(self): + return self.manager.sess.zone + + def __eq__(self, other_group): + return self.name == other_group.name + def __init__(self, manager, result=None): self.manager = manager if result: From e2da17bbcc1e548e2c83eadad778e7b81d838d23 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Sun, 25 Oct 2026 13:14:54 -0400 Subject: [PATCH 3/8] [_847] user equality operator (for group membership test) --- irods/user.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/irods/user.py b/irods/user.py index 453f0ed0b..849c0e8dd 100644 --- a/irods/user.py +++ b/irods/user.py @@ -17,6 +17,14 @@ def remove_quota(self, resource="total"): def set_quota(self, amount, resource="total"): self.manager.set_quota(self.name, amount, resource=resource) + # TODO: add issue for iRODSUser & iRODSGroup equality operators + def __eq__(self, other_user): + # For the case of (iRODSUser_instance == iRODSGroup_instance), iRODSGroup.__eq__ is the fallback + # chosen by the Python interpreter when NotImplemented is returned. + if not isinstance(other_user, iRODSUser): + return NotImplemented + return (self.name, self.zone) == (other_user.name, other_user.zone) + def __init__(self, manager, result=None): self.manager = manager if result: @@ -87,7 +95,9 @@ def zone(self): return self.manager.sess.zone def __eq__(self, other_group): - return self.name == other_group.name + if not isinstance(other_group, (iRODSUser,iRODSGroup)): + return NotImplemented + return (self.name, self.zone) == (other_group.name, other_group.zone) def __init__(self, manager, result=None): self.manager = manager From 606d8e130e2cbca79465b162ddbf77c1413f6bcd Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Mon, 2 Nov 2026 20:22:00 -0500 Subject: [PATCH 4/8] [_561] test passes --- irods/test/access_test.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/irods/test/access_test.py b/irods/test/access_test.py index 458293f83..7bf0c1133 100644 --- a/irods/test/access_test.py +++ b/irods/test/access_test.py @@ -1,18 +1,21 @@ #! /usr/bin/env python +import io import logging import os import sys import textwrap import unittest +import uuid import irods.access from irods.access import ACLOperation, iRODSAccess from irods.collection import iRODSCollection from irods.column import In, Like -from irods.exception import UserDoesNotExist +from irods.exception import CAT_NO_ROWS_FOUND, UserDoesNotExist from irods.models import Collection, DataObject, User from irods.path import iRODSPath +from irods.query import SpecificQuery from irods.session import iRODSSession from irods.test import helpers from irods.user import iRODSUser @@ -583,6 +586,7 @@ def test_get_maximum_permission_for_path__issue_561(self): user = group = coll = None ses = self.sess my_specific_query=None + query_alias = None try: coll = ses.collections.create('/tempZone/home/public/folder_561') data = ses.data_objects.create('/tempZone/home/public/folder_561/file') @@ -598,13 +602,21 @@ def test_get_maximum_permission_for_path__issue_561(self): ses.acls.set(iRODSAccess(USER_PERM, obj.path, user.name)) ses.acls.set(iRODSAccess(GROUP_PERM, obj.path, group.name)) + sql_arg = {} try: - SpecificQuery(ses, alias='findQueryByAlias', args=(alias_for_get_maximum_permission_for_path,)).execute() + SpecificQuery(ses, + alias='findQueryByAlias', + args=(query_alias:=alias_for_get_maximum_permission_for_path,)).execute() except CAT_NO_ROWS_FOUND: query_alias = str(uuid.uuid1()), - my_specific_query = SpecificQuery(ses, sql=SQL, alias=query_alias) + sql_arg = {'sql':SQL} + + my_specific_query = SpecificQuery(ses, alias=query_alias, **sql_arg) + if sql_arg: + my_specific_query.register() logger=logging.getLogger('issue_561_query_type_detection') + logger.setLevel(logging.DEBUG) for is_member in (False, True): if is_member: group.addmember(user.name,user.zone) @@ -624,7 +636,7 @@ def test_get_maximum_permission_for_path__issue_561(self): for query_index in ('SPECIFIC', 'GENERAL'): with self.subTest(f"Issue 561 subTest: {is_member=} {usr=} {obj=} {query_index=}"): try: - logger.addHandler(handler:=StreamHandler(stream:=io.StringIO())) + logger.addHandler(handler:=logging.StreamHandler(stream:=io.StringIO())) permission = ses.acls.get_maximum_permission_for_path( usr, **query_options[query_index], @@ -632,7 +644,7 @@ def test_get_maximum_permission_for_path__issue_561(self): ).as_string self.assertEqual(permission, expected_permission) # Check logging output to verify which code path was taken. - self.assertEqual(stream.getvalue(), query_index) + self.assertEqual(stream.getvalue().rstrip(), query_index) finally: logger.removeHandler(handler) finally: @@ -643,8 +655,8 @@ def test_get_maximum_permission_for_path__issue_561(self): user.remove() if group: group.remove() - if specific_query: - specific_query.remove() + if my_specific_query and (query_alias != alias_for_get_maximum_permission_for_path): + my_specific_query.remove() if __name__ == "__main__": From 478a1a60a42a50ce9760171339a63bb48e3f533e Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Fri, 6 Nov 2026 09:26:17 -0500 Subject: [PATCH 5/8] [_561] correct return type --- irods/manager/access_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irods/manager/access_manager.py b/irods/manager/access_manager.py index 25743950e..ddcfd1ee5 100644 --- a/irods/manager/access_manager.py +++ b/irods/manager/access_manager.py @@ -56,7 +56,7 @@ def get_maximum_permission_for_path(self, coll : [str|iRODSCollection|None]=None, data : [str|iRODSDataObject|None]=None, specific_query : [SpecificQuery|str|None]='', - ) -> int|str: + ) -> _iRODSPermission: """ Return the highest permission the named user/group has with respect to the given data object or collection. In calculating this result, all ACLs and From 9eb7eabc07dd83bd295575fd936b06e047a06813 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Mon, 9 Nov 2026 10:19:23 -0500 Subject: [PATCH 6/8] [_846] update python versions for GitHub test and source check actions --- .github/workflows/main.yml | 2 +- .github/workflows/run-test-programs-single-node.yml | 2 +- .github/workflows/run-test-suite-multiple-node.yml | 2 +- .github/workflows/run-test-suite-single-node.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1c9fcf912..3f8c06b03 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - python-version: ['3.9'] + python-version: ['3.10'] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/run-test-programs-single-node.yml b/.github/workflows/run-test-programs-single-node.yml index 03f35ad2a..211d4f1e4 100644 --- a/.github/workflows/run-test-programs-single-node.yml +++ b/.github/workflows/run-test-programs-single-node.yml @@ -20,7 +20,7 @@ jobs: working-directory: ./test_harness/single_node strategy: matrix: - python: ['3.9','3.13'] + python: ['3.10','3.15'] irods_server: ['4.3.5','5.0.2'] steps: diff --git a/.github/workflows/run-test-suite-multiple-node.yml b/.github/workflows/run-test-suite-multiple-node.yml index 3d2f20c2f..de621c3da 100644 --- a/.github/workflows/run-test-suite-multiple-node.yml +++ b/.github/workflows/run-test-suite-multiple-node.yml @@ -14,7 +14,7 @@ jobs: working-directory: ./test_harness/multiple_node strategy: matrix: - python: ['3.9','3.13'] + python: ['3.10','3.15'] irods_server: ['4.3.5','5.0.2'] steps: diff --git a/.github/workflows/run-test-suite-single-node.yml b/.github/workflows/run-test-suite-single-node.yml index d65dfa2ed..37cdfc838 100644 --- a/.github/workflows/run-test-suite-single-node.yml +++ b/.github/workflows/run-test-suite-single-node.yml @@ -14,7 +14,7 @@ jobs: working-directory: ./test_harness/single_node strategy: matrix: - python: ['3.9','3.13'] + python: ['3.10','3.15'] irods_server: ['4.3.5','5.0.2'] steps: From b2d001e812275cd6e01c728ff949cec1a7c9c682 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Mon, 9 Nov 2026 16:32:25 -0500 Subject: [PATCH 7/8] [_561] corrections for review comments --- irods/manager/access_manager.py | 40 ++++++++++++++++----------------- irods/test/access_test.py | 8 +++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/irods/manager/access_manager.py b/irods/manager/access_manager.py index ddcfd1ee5..f58cb81a8 100644 --- a/irods/manager/access_manager.py +++ b/irods/manager/access_manager.py @@ -53,8 +53,8 @@ class _iRODSPermission(typing.NamedTuple): class AccessManager(Manager): def get_maximum_permission_for_path(self, user : [iRODSUser|iRODSGroup], - coll : [str|iRODSCollection|None]=None, - data : [str|iRODSDataObject|None]=None, + collection : [str|iRODSCollection|None]=None, + data_object : [str|iRODSDataObject|None]=None, specific_query : [SpecificQuery|str|None]='', ) -> _iRODSPermission: """ @@ -62,12 +62,12 @@ def get_maximum_permission_for_path(self, given data object or collection. In calculating this result, all ACLs and group memberships are factored in. - Only one of 'data' or 'coll' may be specified. + Only one of 'data_object' or 'collection' may be specified. Args: user: an iRODSUser or iRODSGroup object for whom the permission is to be calculated. - coll: an iRODS logical path or iRODSCollection. - data: an iRODS logical path or iRODSDataObject. + collection: an iRODS logical path or iRODSCollection. + data_object: an iRODS logical path or iRODSDataObject. specific_query (default ""): a SpecificQuery object or an alias specifying the specific query used for the calculation. The default value of "" will be interpreted to mean the designated specific query preconfigured for this express @@ -107,8 +107,8 @@ def denullify(s): args=[ user.name, getattr(user,'zone',ses.zone), - denullify(coll.path if isinstance(coll, iRODSCollection) else coll), - denullify(data.path if isinstance(data, iRODSDataObject) else data), + denullify(collection.path if isinstance(collection, iRODSCollection) else collection), + denullify(data_object.path if isinstance(data_object, iRODSDataObject) else data_object), ] ) max_permission = self._get_maximum_permission_for_path_via_SpecificQuery(_specific_query) @@ -119,15 +119,15 @@ def denullify(s): groups = {g[Group.id] for g in ses.query(Group, User).filter(Group.id != user.id, User.id == user.id)} groups |= {user.id} - if data: - if isinstance(data, str): - collN = irods_dirname(data) - dataN = irods_basename(data) + if data_object: + if isinstance(data_object, str): + collN = irods_dirname(data_object) + dataN = irods_basename(data_object) join_criteria = [DataObject.name == dataN, Collection.name == collN] - elif isinstance(data, iRODSDataObject): - join_criteria = [ DataAccess.data_id == data.id] + elif isinstance(data_object, iRODSDataObject): + join_criteria = [ DataAccess.data_id == data_object.id] else: - msg="'data' parameter must be full logical path or data object" + msg="'data_object' parameter must be full logical path or data object" raise ValueError(msg) q = ses.query(DataAccess.type).filter( @@ -137,13 +137,13 @@ def denullify(s): res = [_[DataAccess.type] for _ in q] - elif coll: - if isinstance(coll, str): - join_criteria = [Collection.name == coll] - elif isinstance(coll, iRODSCollection): - join_criteria = [CollectionAccess.access_id == coll.id] # TODO: access_id is not an accurate name, should be coll_id + elif collection: + if isinstance(collection, str): + join_criteria = [Collection.name == collection] + elif isinstance(collection, iRODSCollection): + join_criteria = [CollectionAccess.access_id == collection.id] # TODO: access_id is not an accurate name, should be coll_id else: - msg="'coll' parameter must be full logical path or collection object" + msg="'collection' parameter must be full logical path or collection object" raise ValueError(msg) q=ses.query(CollectionAccess).filter( diff --git a/irods/test/access_test.py b/irods/test/access_test.py index 7bf0c1133..9b0aa065b 100644 --- a/irods/test/access_test.py +++ b/irods/test/access_test.py @@ -624,10 +624,10 @@ def test_get_maximum_permission_for_path__issue_561(self): max_user = usr if usr not in group.members else group expected_permission = test_perms[max_user.name] for obj_type, obj in ( - ("coll", coll), - ("coll", coll.path), - ("data", data), - ("data", data.path), + ("collection", coll), + ("collection", coll.path), + ("data_object", data), + ("data_object", data.path), ): query_options = { 'GENERAL':{'specific_query':None}, From 68143c16ab437bcd47fb3ed8cfa36a267ee7eca8 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Mon, 9 Nov 2026 16:34:58 -0500 Subject: [PATCH 8/8] [_561] return type includes None --- irods/manager/access_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irods/manager/access_manager.py b/irods/manager/access_manager.py index f58cb81a8..0683a1278 100644 --- a/irods/manager/access_manager.py +++ b/irods/manager/access_manager.py @@ -56,7 +56,7 @@ def get_maximum_permission_for_path(self, collection : [str|iRODSCollection|None]=None, data_object : [str|iRODSDataObject|None]=None, specific_query : [SpecificQuery|str|None]='', - ) -> _iRODSPermission: + ) -> _iRODSPermission|None: """ Return the highest permission the named user/group has with respect to the given data object or collection. In calculating this result, all ACLs and