66import os
77import io
88import re
9- from typing import Any
9+ from typing import Any , Callable , Self
1010import importlib
1111import logging
1212import pytest # for explicit fail calls, see _pytestFail
@@ -89,6 +89,9 @@ class Authenticator:
8989 _AUTH_SCHEMES .update (_TOKEN_SCHEMES )
9090 _AUTH_SCHEMES .update (_PASS_SCHEMES )
9191
92+ # authenticator login/pass hook
93+ _AuthHook = Callable [[str , str | None ], None ]
94+
9295 def __init__ (self ,
9396 allow : list [str ] = ["bearer" , "basic" , "param" , "none" ],
9497 # parameter names for "basic" and "param"
@@ -124,6 +127,9 @@ def __init__(self,
124127 assert ptype in ("json" , "data" )
125128 self ._ptype = ptype
126129
130+ # _AuthHook|None, but python cannot stand it:-(
131+ self ._auth_hook : Any = None
132+
127133 # password and token credentials, cookies
128134 self ._passes : dict [str , str ] = {}
129135 self ._tokens : dict [str , str ] = {}
@@ -138,6 +144,9 @@ def _set(self, key: str, val: str|None, store: dict[str, str]):
138144 assert isinstance (val , str )
139145 store [key ] = val
140146
147+ def setHook (self , hook : _AuthHook ):
148+ self ._auth_hook = hook
149+
141150 def setPass (self , login : str , pw : str | None ):
142151 """Associate a password to a user.
143152
@@ -146,6 +155,7 @@ def setPass(self, login: str, pw: str|None):
146155 if not self ._has_pass :
147156 raise AuthError ("cannot set password, no password scheme allowed" )
148157 self ._set (login , pw , self ._passes )
158+ _ = self ._auth_hook and self ._auth_hook (login , pw )
149159
150160 def setPasses (self , pws : list [str ]):
151161 """Associate a list of *login:password*."""
@@ -298,11 +308,17 @@ class Client:
298308 :param default_login: When ``login`` is not set.
299309 """
300310
311+ # client login/pass hook (with mypy workaround)
312+ AuthHook = Callable [[Self , str , str | None ], None ] # type: ignore
313+
301314 def __init__ (self , auth : Authenticator , default_login : str | None = None ):
302315 self ._auth = auth
303316 self ._cookies : dict [str , dict [str , str ]] = {} # login -> name -> value
304317 self ._default_login = default_login
305318
319+ def setHook (self , hook : AuthHook ):
320+ self ._auth .setHook (lambda u , p : hook (self , u , p ))
321+
306322 def setToken (self , login : str , token : str | None ):
307323 """Associate a token to a login, *None* to remove."""
308324 self ._auth .setToken (login , token )
0 commit comments