@@ -131,16 +131,58 @@ def __init__(self, operation, avu, **kw):
131131
132132
133133class iRODSMetaCollection :
134+ def __setattr__ (self , name , value ):
135+ """
136+ Protect the virtual, read-only attributes such as 'admin', 'timestamps', etc.,
137+ from being written or created as concrete attributes, which would interfere with
138+ __getattr__'s intended operation for these cases.
139+
140+ Args:
141+ name: the name of the attribute to be written.
142+ value: the value to be written to the attribute.
143+
144+ Raises:
145+ AttributeError: on any attempt to write to these special attributes.
146+ """
147+ from irods .manager .metadata_manager import _MetadataManager_opts_initializer
148+
149+ if name in _MetadataManager_opts_initializer :
150+ msg = (
151+ f"""The "{ name } " attribute is a special one, settable only via a """
152+ f"""call on the object. For example: admin_view = data_obj.metadata({ name } =<value>)"""
153+ )
154+ raise AttributeError (msg )
155+
156+ super ().__setattr__ (name , value )
157+
134158 def __getattr__ (self , name ):
159+ """
160+ Expose certain settable flags (e.g. "admin", "timestamps") as virtual, read-only
161+ "attributes." The names of these special attributes appear as the keys of the
162+ _MetadataManager_opts_initializer dictionary.
163+
164+ Args:
165+ name: the name of the attribute to be fetched.
166+
167+ Returns:
168+ the value of the named attribute.
169+
170+ Raises:
171+ AttributeError: because this is the protocol for deferring to __getattr__'s
172+ default behavior for the case in which none of the special attribute keys are
173+ a match for 'name'.
174+ """
135175 from irods .manager .metadata_manager import _MetadataManager_opts_initializer
176+
136177 # Separating _MetadataManager_opts_initializer from the MetadataManager class
137- # prevents # the possibility of arbitrary access by copy.copy() to parts of
178+ # prevents the possibility of arbitrary access by copy.copy() to parts of
138179 # our object's state before they have been initialized, as it is known to do
139180 # by calling hasattr on the "__setstate__" attribute. The result of such
140181 # unfettered access is infinite recursion. See:
141182 # https://nedbatchelder.com/blog/201010/surprising_getattr_recursion
183+
142184 if name in _MetadataManager_opts_initializer :
143- return self ._manager ._opts [name ]
185+ return self ._manager ._opts [name ] # noqa: SLF001
144186 raise AttributeError
145187
146188 def __call__ (self , ** opts ):
0 commit comments