3030
3131from __future__ import absolute_import , annotations
3232
33- from dataclasses import dataclass
3433from typing import Optional , Type , TypeVar
3534from xml .etree import ElementTree as ET
3635
@@ -80,7 +79,6 @@ def status_code(self) -> int:
8079A = TypeVar ("A" , bound = "S3Error" )
8180
8281
83- @dataclass (frozen = True )
8482class S3Error (MinioException ):
8583 """
8684 Raised to indicate that error response is received
@@ -92,24 +90,65 @@ class S3Error(MinioException):
9290 resource : Optional [str ]
9391 request_id : Optional [str ]
9492 host_id : Optional [str ]
95- bucket_name : Optional [str ] = None
96- object_name : Optional [str ] = None
93+ bucket_name : Optional [str ]
94+ object_name : Optional [str ]
95+
96+ _EXC_MUTABLES = {"__traceback__" , "__context__" , "__cause__" }
97+
98+ def __init__ ( # pylint: disable=too-many-positional-arguments
99+ self ,
100+ response : BaseHTTPResponse ,
101+ code : Optional [str ],
102+ message : Optional [str ],
103+ resource : Optional [str ],
104+ request_id : Optional [str ],
105+ host_id : Optional [str ],
106+ bucket_name : Optional [str ] = None ,
107+ object_name : Optional [str ] = None ,
108+ ):
109+ object .__setattr__ (self , "response" , response )
110+ object .__setattr__ (self , "code" , code )
111+ object .__setattr__ (self , "message" , message )
112+ object .__setattr__ (self , "resource" , resource )
113+ object .__setattr__ (self , "request_id" , request_id )
114+ object .__setattr__ (self , "host_id" , host_id )
115+ object .__setattr__ (self , "bucket_name" , bucket_name )
116+ object .__setattr__ (self , "object_name" , object_name )
117+
118+ bucket_message = f", bucket_name: { bucket_name } " if bucket_name else ""
119+ object_message = f", object_name: { object_name } " if object_name else ""
97120
98- def __post_init__ (self ):
99- bucket_message = (
100- (", bucket_name: " + self .bucket_name )
101- if self .bucket_name else ""
102- )
103- object_message = (
104- (", object_name: " + self .object_name )
105- if self .object_name else ""
106- )
107121 super ().__init__ (
108- f"S3 operation failed; code: { self . code } , message: { self . message } , "
109- f"resource: { self . resource } , request_id: { self . request_id } , "
110- f"host_id: { self . host_id } { bucket_message } { object_message } "
122+ f"S3 operation failed; code: { code } , message: { message } , "
123+ f"resource: { resource } , request_id: { request_id } , "
124+ f"host_id: { host_id } { bucket_message } { object_message } "
111125 )
112126
127+ # freeze after init
128+ object .__setattr__ (self , "_is_frozen" , True )
129+
130+ def __setattr__ (self , name , value ):
131+ if name in self ._EXC_MUTABLES :
132+ object .__setattr__ (self , name , value )
133+ return
134+ if getattr (self , "_is_frozen" , False ):
135+ raise AttributeError (
136+ f"{ self .__class__ .__name__ } is frozen and "
137+ "does not allow attribute assignment"
138+ )
139+ object .__setattr__ (self , name , value )
140+
141+ def __delattr__ (self , name ):
142+ if name in self ._EXC_MUTABLES :
143+ object .__delattr__ (self , name )
144+ return
145+ if getattr (self , "_is_frozen" , False ):
146+ raise AttributeError (
147+ f"{ self .__class__ .__name__ } is frozen and "
148+ "does not allow attribute deletion"
149+ )
150+ object .__delattr__ (self , name )
151+
113152 @classmethod
114153 def fromxml (cls : Type [A ], response : BaseHTTPResponse ) -> A :
115154 """Create new object with values from XML element."""
@@ -126,7 +165,7 @@ def fromxml(cls: Type[A], response: BaseHTTPResponse) -> A:
126165 )
127166
128167 def copy (self , code : str , message : str ) -> S3Error :
129- """Make a copy with replace code and message."""
168+ """Make a copy with replaced code and message."""
130169 return S3Error (
131170 response = self .response ,
132171 code = code ,
@@ -138,6 +177,40 @@ def copy(self, code: str, message: str) -> S3Error:
138177 object_name = self .object_name ,
139178 )
140179
180+ def __repr__ (self ):
181+ return (
182+ f"S3Error(code={ self .code !r} , message={ self .message !r} , "
183+ f"resource={ self .resource !r} , request_id={ self .request_id !r} , "
184+ f"host_id={ self .host_id !r} , bucket_name={ self .bucket_name !r} , "
185+ f"object_name={ self .object_name !r} )"
186+ )
187+
188+ def __eq__ (self , other ):
189+ if not isinstance (other , S3Error ):
190+ return NotImplemented
191+ return (
192+ self .code == other .code
193+ and self .message == other .message
194+ and self .resource == other .resource
195+ and self .request_id == other .request_id
196+ and self .host_id == other .host_id
197+ and self .bucket_name == other .bucket_name
198+ and self .object_name == other .object_name
199+ )
200+
201+ def __hash__ (self ):
202+ return hash (
203+ (
204+ self .code ,
205+ self .message ,
206+ self .resource ,
207+ self .request_id ,
208+ self .host_id ,
209+ self .bucket_name ,
210+ self .object_name ,
211+ )
212+ )
213+
141214
142215class MinioAdminException (Exception ):
143216 """Raised to indicate admin API execution error."""
0 commit comments