66import hmac
77import math
88import warnings
9+ from abc import ABC , abstractmethod
910from collections .abc import Callable
1011from typing import Any
1112
@@ -158,7 +159,7 @@ def must_update_salt(salt: str, expected_entropy: int) -> bool:
158159 return len (salt ) * math .log2 (len (RANDOM_STRING_CHARS )) < expected_entropy
159160
160161
161- class BasePasswordHasher :
162+ class BasePasswordHasher ( ABC ) :
162163 """
163164 Abstract base class for password hashers
164165
@@ -181,29 +182,28 @@ def salt(self) -> str:
181182 char_count = math .ceil (self .salt_entropy / math .log2 (len (RANDOM_STRING_CHARS )))
182183 return get_random_string (char_count , allowed_chars = RANDOM_STRING_CHARS )
183184
185+ @abstractmethod
184186 def verify (self , password : str , encoded : str ) -> bool :
185187 """Check if the given password is correct."""
186- raise NotImplementedError (
187- "subclasses of BasePasswordHasher must provide a verify() method"
188- )
188+ ...
189189
190190 def _check_encode_args (self , password : str , salt : str ) -> None :
191191 if password is None :
192192 raise TypeError ("password must be provided." )
193193 if not salt or "$" in salt :
194194 raise ValueError ("salt must be provided and cannot contain $." )
195195
196+ @abstractmethod
196197 def encode (self , password : str , salt : str ) -> str :
197198 """
198199 Create an encoded database value.
199200
200201 The result is normally formatted as "algorithm$salt$hash" and
201202 must be fewer than 128 characters.
202203 """
203- raise NotImplementedError (
204- "subclasses of BasePasswordHasher must provide an encode() method"
205- )
204+ ...
206205
206+ @abstractmethod
207207 def decode (self , encoded : str ) -> dict [str , Any ]:
208208 """
209209 Return a decoded database value.
@@ -212,20 +212,17 @@ def decode(self, encoded: str) -> dict[str, Any]:
212212 `salt`. Extra keys can be algorithm specific like `iterations` or
213213 `work_factor`.
214214 """
215- raise NotImplementedError (
216- "subclasses of BasePasswordHasher must provide a decode() method."
217- )
215+ ...
218216
217+ @abstractmethod
219218 def safe_summary (self , encoded : str ) -> dict [str , Any ]:
220219 """
221220 Return a summary of safe values.
222221
223222 The result is a dictionary and will be used where the password field
224223 must be displayed to construct a safe representation of the password.
225224 """
226- raise NotImplementedError (
227- "subclasses of BasePasswordHasher must provide a safe_summary() method"
228- )
225+ ...
229226
230227 def must_update (self , encoded : str ) -> bool :
231228 return False
0 commit comments