22import copy
33from irods .collection import iRODSCollection
44from irods .data_object import iRODSDataObject
5- from irods .path import iRODSPath
65
7-
8- _ichmod_listed_permissions = (
6+ _permissions = (
97 "own" ,
108 "delete_object" ,
119 "write" ,
2220
2321
2422class _Access_LookupMeta (type ):
25-
2623 @staticmethod
2724 def _codes ():
2825 return collections .OrderedDict (
2926 (key_ , value_ )
3027 for key_ , value_ in sorted (
31- dict (
32- # copied from iRODS source code in
28+ {
29+ # adapted from iRODS source code in
3330 # ./server/core/include/irods/catalog_utilities.hpp:
34- null = 1000 ,
35- execute = 1010 ,
36- read_annotation = 1020 ,
37- read_system_metadata = 1030 ,
38- read_metadata = 1040 ,
39- read_object = 1050 ,
40- write_annotation = 1060 ,
41- create_metadata = 1070 ,
42- modify_metadata = 1080 ,
43- delete_metadata = 1090 ,
44- administer_object = 1100 ,
45- create_object = 1110 ,
46- modify_object = 1120 ,
47- delete_object = 1130 ,
48- create_token = 1140 ,
49- delete_token = 1150 ,
50- curate = 1160 ,
51- own = 1200 ,
52- ) .items (),
31+ " null" : 1000 ,
32+ " execute" : 1010 ,
33+ " read_annotation" : 1020 ,
34+ " read_system_metadata" : 1030 ,
35+ " read_metadata" : 1040 ,
36+ " read_object" : 1050 ,
37+ " write_annotation" : 1060 ,
38+ " create_metadata" : 1070 ,
39+ " modify_metadata" : 1080 ,
40+ " delete_metadata" : 1090 ,
41+ " administer_object" : 1100 ,
42+ " create_object" : 1110 ,
43+ " modify_object" : 1120 ,
44+ " delete_object" : 1130 ,
45+ " create_token" : 1140 ,
46+ " delete_token" : 1150 ,
47+ " curate" : 1160 ,
48+ " own" : 1200 ,
49+ } .items (),
5350 key = lambda _ : _ [1 ],
5451 )
55- if key_ in _ichmod_listed_permissions
52+ if key_ in _permissions
5653 )
5754
5855 @property
59- def codes (metaclass_target ):
60- return metaclass_target ._codes ()
56+ def codes (cls ):
57+ return cls ._codes ()
6158
6259 @property
63- def strings (metaclass_target ):
64- return collections .OrderedDict ((number , string ) for string , number in metaclass_target ._codes ().items ())
60+ def strings (cls ):
61+ return collections .OrderedDict ((number , string ) for string , number in cls ._codes ().items ())
6562
6663 def __getitem__ (self , key ):
6764 return self .codes [key ]
@@ -102,37 +99,95 @@ def __init__(self, access_name, path, user_name, user_zone, user_type):
10299 self .user_type = user_type
103100
104101 def __lt__ (self , other ):
105- return (self .access_name , self .user_name , self .user_zone , iRODSPath (self .path )) < (
102+ return (self .access_name , self .user_name , self .user_zone , str (self .path )) < (
106103 other .access_name ,
107104 other .user_name ,
108105 other .user_zone ,
109- iRODSPath (other .path ),
106+ str (other .path ),
110107 )
111108
112109 def __eq__ (self , other ):
113110 return (
114111 self .access_name == other .access_name
115- and iRODSPath (self .path ) == iRODSPath (other .path )
112+ and str (self .path ) == str (other .path )
116113 and self .user_name == other .user_name
117114 and self .user_zone == other .user_zone
118115 )
119116
120117 def __hash__ (self ):
121- return hash ((self .access_name , iRODSPath (self .path ), self .user_name , self .user_zone ))
118+ return hash ((self .access_name , str (self .path ), self .user_name , self .user_zone ))
119+
120+ def normalize (self , local_zone = "" ):
121+ """
122+ Create a normalized version of the object for comparison in sorting or determining equivalence.
123+
124+ Args:
125+ local_zone: the name of the home zone, if any, in which client user directly authenticates.
126+ The purpose is zone name normalization; if this parameter is a nonzero-length string which
127+ matches the zone_name in the source object, the copy will contain a null zone_name field.
128+
129+ Returns:
130+ The normalized copy of the source object. In practice, this will be an ACLOperation or iRODSAccess
131+ object, according to the type of the source object.
132+ """
133+ normalized_form = self .copy (decanonicalize = - 1 , implied_zone = local_zone )
134+ normalized_form .path = ""
135+ return normalized_form
122136
123137 def copy (self , decanonicalize = False , implied_zone = '' ):
138+ """
139+ Create a copy of the object, possibly in a normalized form.
140+
141+ Args:
142+ decanonicalize: Whether to modify the access_name field to a more human-readable form
143+ (when 1 or True) or a more standard form (when -1). If the former, then a more
144+ organic style is favored, i.e. "read" and "write". If the latter, the new
145+ access_name will be more machine-friendly for operators __lt__ (for sorting) and
146+ __eq__ (for equivalence or use with 'in'). If equal to 0 (or False), no adjustment
147+ is done.
148+ implied_zone: If a nonzero-length name, compare this against the zone_name field of the
149+ old object, and if they match, force the zone_name to zero-length in the new object.
150+
151+ Returns:
152+ A copy of the invoking object, normalized if requested.
153+
154+ Raises:
155+ RuntimeError: if decanonicalize parameter is not one of {-1,0,False,1,True}.
156+ """
124157 other = copy .deepcopy (self )
125158
126- if decanonicalize :
127- replacement_string = {
128- "read object" : "read" ,
129- "read_object" : "read" ,
130- "modify object" : "write" ,
131- "modify_object" : "write" ,
132- }.get (self .access_name )
133- other .access_name = replacement_string if replacement_string is not None else self .access_name
159+ access_name = self .access_name
160+
161+ if decanonicalize == 1 :
162+ if (
163+ new_access_name := {
164+ "read object" : "read" ,
165+ "read_object" : "read" ,
166+ "modify object" : "write" ,
167+ "modify_object" : "write" ,
168+ }.get (access_name )
169+ ) is not None :
170+ access_name = new_access_name
171+ elif decanonicalize == - 1 :
172+ # Canonicalize, ie. change out old access_name for an unambiguous "standard" value.
173+ access_name = access_name .replace (" " , "_" )
174+ if (
175+ new_access_name := {
176+ "read" : "read_object" ,
177+ "write" : "modify_object" ,
178+ }.get (access_name )
179+ ) is not None :
180+ access_name = new_access_name
181+ elif decanonicalize == 0 :
182+ pass
183+ else :
184+ msg = "Improper value for 'decanonicalize' parameter"
185+ raise RuntimeError (msg )
186+
187+ other .access_name = access_name
134188
135- # Useful if we wish to force an explicitly specified local zone to an implicit zone spec in the copy, for equality testing:
189+ # Useful if we wish to force an explicitly specified local zone to an implicit zone spec in the copy, for
190+ # equality testing:
136191 if '' != implied_zone == other .user_zone :
137192 other .user_zone = ''
138193
@@ -146,14 +201,32 @@ def __repr__(self):
146201
147202
148203class iRODSAccess (_iRODSAccess_base , metaclass = _Access_LookupMeta ):
149- def __init__ (self , access_name , path , user_name = "" , user_zone = "" , user_type = None ):
204+ """
205+ Represents an ACL in iRODS.
206+
207+ An instance of this class functions as a data container to convey information to the iRODS
208+ server (in the `set` call) and back again to the client again (in the `get` call).
209+ """
210+
211+ def __init__ (self , access_name , path , user_name = "" , user_zone = "" , user_type = None ): # noqa: D107
150212 self .codes = self .__class__ .codes
151213 self .strings = self .__class__ .strings
152214 super ().__init__ (access_name , path , user_name , user_zone , user_type )
153215
154216
155217class ACLOperation (iRODSAccess ):
156- def __init__ (self , access_name : str , user_name : str = "" , user_zone : str = "" ):
218+ """
219+ Represents an operation to be performed in iRODS' atomic ACL api.
220+
221+ Similar to its base class, iRODSAccess, this class names an ACL to be set on an object.
222+ It differs, however, in that it forgoes option to store a logical object path. (In the atomic
223+ API call, there is always a single logical path to which all such operations apply, thus
224+ it is appropriate that the path parameter is in a location separate from the operations.)
225+ """ # noqa: D400
226+
227+ # ruff: noqa: D105 on
228+
229+ def __init__ (self , access_name : str , user_name : str = "" , user_zone : str = "" ): # noqa: D107
157230 super ().__init__ (
158231 access_name = access_name ,
159232 path = "" ,
@@ -172,6 +245,16 @@ def __eq__(self, other):
172245 other .user_zone ,
173246 )
174247
248+ def __hash__ (self ):
249+
250+ # Hash in a way consistent with an iRODSAccess having path "".
251+ return hash ((
252+ self .access_name ,
253+ "" , # path
254+ self .user_name ,
255+ self .user_zone ,
256+ ))
257+
175258 def __lt__ (self , other ):
176259 return (
177260 self .access_name ,
@@ -186,19 +269,20 @@ def __lt__(self, other):
186269 def __repr__ (self ):
187270 return f"<ACLOperation { self .access_name } { self .user_name } { self .user_zone } >"
188271
272+ # ruff: noqa: D105 off
273+
189274
190275(
191- _ichmod_synonym_mapping := {
192- # syn : canonical
276+ _synonym_mapping := {
193277 "write" : "modify_object" ,
194278 "read" : "read_object" ,
195279 }
196- ).update ((key .replace ("_" , " " ), key ) for key in iRODSAccess .codes . keys () )
280+ ).update ((key .replace ("_" , " " ), key ) for key in iRODSAccess .codes )
197281
198282
199283all_permissions = {
200284 ** iRODSAccess .codes ,
201- ** {key : iRODSAccess .codes [_ichmod_synonym_mapping [key ]] for key in _ichmod_synonym_mapping },
285+ ** {key : iRODSAccess .codes [_synonym_mapping [key ]] for key in _synonym_mapping },
202286}
203287
204288
0 commit comments