Skip to content

Commit fb6e373

Browse files
committed
misc corrections / ruff lint and format
1 parent 228bf86 commit fb6e373

5 files changed

Lines changed: 141 additions & 48 deletions

File tree

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,6 +2118,34 @@ membership, this can be achieved with another query.
21182118
`<session>.permissions` was therefore removed in v2.0.0
21192119
in favor of `<session>.acls`.
21202120

2121+
Atomic ACLs
2122+
-----------
2123+
2124+
A list of permissions may be added to an object atomically using
2125+
the AccessManager's apply_atomic_operations method:
2126+
```
2127+
from irods.access import ACLOperation
2128+
from irods.helpers import home_collection
2129+
session = irods.helpers.make_session()
2130+
myCollection = session.collections.create(f"{home_collection(session).path}/newCollection")
2131+
2132+
session.acls.apply_atomic_operations(myCollection.path,
2133+
*[ACLOperation("read", "public"),
2134+
ACLOperation("write", "bob", "otherZone")
2135+
])
2136+
```
2137+
ACLOperation objects form a linear order with iRODSAccess objects, and
2138+
indeed are subclassed from them as well, allowing equivalency testing:
2139+
2140+
Thus, for example:
2141+
```
2142+
ACLOperation('read','public') in sess.acls.get(object)
2143+
```
2144+
is a valid operation. Consequently, any client application that habitually
2145+
caches object permissions could use similar code to check new ACLOperations against the cache
2146+
and conceivably be able to optimize size of an atomic ACLs request by eliminating
2147+
any ACLOperations that might have been redundant.
2148+
21212149
Quotas (v2.0.0)
21222150
---------------
21232151

irods/access.py

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@
55
from irods.path import iRODSPath
66

77

8+
_ichmod_listed_permissions = (
9+
"own",
10+
"delete_object",
11+
"write",
12+
"modify_object",
13+
"create_object",
14+
"delete_metadata",
15+
"modify_metadata",
16+
"create_metadata",
17+
"read",
18+
"read_object",
19+
"read_metadata",
20+
"null",
21+
)
22+
23+
824
class _Access_LookupMeta(type):
925
def __getitem__(self, key):
1026
return self.codes[key]
@@ -28,6 +44,7 @@ def to_int(cls, key):
2844
def to_string(cls, key):
2945
return cls.strings[key]
3046

47+
# noqa: RUF012 - Cannot change in minor release
3148
codes = collections.OrderedDict(
3249
(key_, value_)
3350
for key_, value_ in sorted(
@@ -55,24 +72,10 @@ def to_string(cls, key):
5572
).items(),
5673
key=lambda _: _[1],
5774
)
58-
if key_
59-
in (
60-
# These are copied from ichmod help text.
61-
"own",
62-
"delete_object",
63-
"write",
64-
"modify_object",
65-
"create_object",
66-
"delete_metadata",
67-
"modify_metadata",
68-
"create_metadata",
69-
"read",
70-
"read_object",
71-
"read_metadata",
72-
"null",
73-
)
75+
if key_ in _ichmod_listed_permissions
7476
)
7577

78+
# noqa: RUF012 - Cannot change in minor release
7679
strings = collections.OrderedDict((number, string) for string, number in codes.items())
7780

7881
def __init__(self, access_name, path, user_name="", user_zone="", user_type=None):
@@ -91,6 +94,14 @@ def __init__(self, access_name, path, user_name="", user_zone="", user_type=None
9194
self.user_zone = user_zone
9295
self.user_type = user_type
9396

97+
def __lt__(self, other):
98+
return (self.access_name, self.user_name, self.user_zone, iRODSPath(self.path)) < (
99+
other.access_name,
100+
other.user_name,
101+
other.user_zone,
102+
iRODSPath(other.path),
103+
)
104+
94105
def __eq__(self, other):
95106
return (
96107
self.access_name == other.access_name
@@ -102,8 +113,9 @@ def __eq__(self, other):
102113
def __hash__(self):
103114
return hash((self.access_name, iRODSPath(self.path), self.user_name, self.user_zone))
104115

105-
def copy(self, decanonicalize=False, ref_zone=''):
116+
def copy(self, decanonicalize=False, implied_zone=''):
106117
other = copy.deepcopy(self)
118+
107119
if decanonicalize:
108120
replacement_string = {
109121
"read object": "read",
@@ -112,8 +124,10 @@ def copy(self, decanonicalize=False, ref_zone=''):
112124
"modify_object": "write",
113125
}.get(self.access_name)
114126
other.access_name = replacement_string if replacement_string is not None else self.access_name
115-
if '' != ref_zone == other.user_zone:
116-
other.user_zone = ''
127+
128+
# Useful if we wish to force an explicitly specified local zone to an implicit zone spec in the copy, for equality testing:
129+
if '' != implied_zone == other.user_zone:
130+
other.user_zone = ''
117131

118132
return other
119133

@@ -124,6 +138,56 @@ def __repr__(self):
124138
return f"<iRODSAccess {access_name} {self.path} {self.user_name}{user_type_hint} {self.user_zone}>"
125139

126140

141+
class ACLOperation(iRODSAccess):
142+
def __init__(self, access_name: str, user_name: str = "", user_zone: str = ""):
143+
super().__init__(
144+
access_name=access_name,
145+
path="",
146+
user_name=user_name,
147+
user_zone=user_zone,
148+
)
149+
150+
def __eq__(self, other):
151+
return (
152+
self.access_name,
153+
self.user_name,
154+
self.user_zone,
155+
) == (
156+
other.access_name,
157+
other.user_name,
158+
other.user_zone,
159+
)
160+
161+
def __lt__(self, other):
162+
return (
163+
self.access_name,
164+
self.user_name,
165+
self.user_zone,
166+
) < (
167+
other.access_name,
168+
other.user_name,
169+
other.user_zone,
170+
)
171+
172+
def __repr__(self):
173+
return f"<ACLOperation {self.access_name} {self.user_name} {self.user_zone}>"
174+
175+
176+
(
177+
_ichmod_synonym_mapping := {
178+
# syn : canonical
179+
"write": "modify_object",
180+
"read": "read_object",
181+
}
182+
).update((key.replace("_", " "), key) for key in iRODSAccess.codes.keys())
183+
184+
185+
all_permissions = {
186+
**iRODSAccess.codes,
187+
**{key: iRODSAccess.codes[_ichmod_synonym_mapping[key]] for key in _ichmod_synonym_mapping},
188+
}
189+
190+
127191
class _iRODSAccess_pre_4_3_0(iRODSAccess):
128192
codes = collections.OrderedDict(
129193
(key.replace("_", " "), value)

irods/api_number.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
"ATOMIC_APPLY_METADATA_OPERATIONS_APN": 20002,
178178
"GET_FILE_DESCRIPTOR_INFO_APN": 20000,
179179
"REPLICA_CLOSE_APN": 20004,
180+
"ATOMIC_APPLY_ACL_OPERATIONS_APN": 20005,
180181
"TOUCH_APN": 20007,
181182
"AUTH_PLUG_REQ_AN": 1201,
182183
"AUTHENTICATION_APN": 110000,

irods/manager/access_manager.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
CollectionAccess,
1515
)
1616
from irods.access import iRODSAccess
17-
import irods.exception as ex
1817
from irods.column import In
1918
from irods.user import iRODSUser
2019

@@ -37,27 +36,26 @@ def users_by_ids(session, ids=()):
3736

3837

3938
class AccessManager(Manager):
40-
41-
def _ACL_operation(self, op_input: iRODSAccess):
39+
@staticmethod
40+
def _to_acl_operation_json(op_input: iRODSAccess):
4241
return {
4342
"acl": op_input.access_name,
4443
"entity_name": op_input.user_name,
45-
**(
46-
{} if not (z := op_input.user_zone)
47-
else {"zone": z}
48-
)
44+
**({} if not (z := op_input.user_zone) else {"zone": z}),
4945
}
5046

51-
def _call_atomic_acl_api(self, logical_path : str, *operations, admin=False):
52-
request_text = {"logical_path": logical_path}
53-
request_text["admin_mode"] = admin
54-
request_text["operations"] = [self._ACL_operation(op) for op in operations]
47+
def apply_atomic_operations(self, logical_path: str, *operations, admin=False):
48+
request_text = {
49+
"logical_path": logical_path,
50+
"admin_mode": admin,
51+
"operations": [self._to_acl_operation_json(op) for op in operations],
52+
}
5553

5654
with self.sess.pool.get_connection() as conn:
5755
request_msg = iRODSMessage(
5856
"RODS_API_REQ",
5957
JSON_Message(request_text, conn.server_version),
60-
int_info=20005,
58+
int_info=api_number["ATOMIC_APPLY_ACL_OPERATIONS_APN"],
6159
)
6260
conn.send(request_msg)
6361
response = conn.recv()

irods/test/access_test.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sys
55
import unittest
66

7-
from irods.access import iRODSAccess
7+
from irods.access import iRODSAccess, ACLOperation
88
from irods.collection import iRODSCollection
99
from irods.column import In, Like
1010
from irods.exception import UserDoesNotExist
@@ -498,43 +498,45 @@ def test_iRODSAccess_cannot_be_constructed_using_unsupported_type__issue_558(sel
498498
)
499499

500500
def test_atomic_acls_505(self):
501-
#import pdb;pdb.set_trace()
502501
ses = self.sess
503-
zone = user1 = user2 = group = None
502+
zone = user1 = user2 = user3 = group = None
504503
try:
505-
zone = ses.zones.create("twilight","remote")
504+
zone = ses.zones.create("twilight", "remote")
506505
user1 = ses.users.create("test_user_505", "rodsuser")
507506
user2 = ses.users.create("rod_serling_505#twilight", "rodsuser")
507+
user3 = ses.users.create("local_test_user_505", "rodsuser")
508508
group = ses.groups.create("test_group_505")
509-
ses.acls._call_atomic_acl_api(
509+
ses.acls.apply_atomic_operations(
510510
self.coll_path,
511-
a1:=iRODSAccess("write", "", user1.name, user1.zone),
512-
a2:=iRODSAccess("read", "", user2.name, user2.zone),
513-
a3:=iRODSAccess("read", "", group.name),
511+
a1:=ACLOperation("write", user1.name, user1.zone),
512+
a2:=ACLOperation("read", user2.name, user2.zone),
513+
a3:=ACLOperation("read", user3.name),
514+
a4:=ACLOperation("read", group.name),
514515
)
515516

516-
accesses = ses.acls.get(self.coll)
517+
normalize = lambda access: access.copy(decanonicalize=True, implied_zone=ses.zone)
517518

518-
# For purposes of equality tests, assign the path name of interest into each ACL.
519-
for p in (a1, a2, a3):
520-
p.path = self.coll_path
519+
accesses = [normalize(acl) for acl in ses.acls.get(self.coll)]
521520

522521
# Assert that the ACLs we added are among those listed for the object in the catalog.
523-
normalize = lambda access: access.copy(decanonicalize=True, ref_zone=ses.zone)
524-
self.assertLess(
525-
set(normalize(_) for _ in (a1,a2,a3)),
526-
set(normalize(_) for _ in accesses)
527-
)
522+
self.assertIn(normalize(a1), accesses)
523+
self.assertIn(normalize(a2), accesses)
524+
self.assertIn(normalize(a3), accesses)
525+
self.assertIn(normalize(a4), accesses)
526+
528527
finally:
529528
if user1:
530529
user1.remove()
531530
if user2:
532531
user2.remove()
532+
if user3:
533+
user3.remove()
533534
if group:
534535
group.remove()
535536
if zone:
536537
zone.remove()
537538

539+
538540
if __name__ == "__main__":
539541
# let the tests find the parent irods lib
540542
sys.path.insert(0, os.path.abspath("../.."))

0 commit comments

Comments
 (0)