Given some examples, I'll do a follow-up blog post.
Given some examples, I'll do a follow-up blog post.
So challenge that is introduced is that you now have hidden state that is dependent on the public state but which cannot be computed by users of the client or the parent class. In fact, code in the parent class can't even know ahead of time what state might be needed to be retained.
I am guessing you will probably be able to propose that authentication be supported by some kind of pluggable authentication interface, but it will still be difficult to deal with the hidden state without introducing assumptions about the auth protocol or the type / nature of the state retained and without having that protrude into the parent interfaces.
Something like:
@dataclass class AuthMethod: credentials: typing.Any cache: typing.Any apply_to_request: typing.Callable[[typing.Any, typing.Any, Request], None]
@dataclass class AuthMapping: method: AuthMethod matches: typing.Callable[[str], bool]
class Client: root_url: str url_format: str auth_methods: typing.List[AuthMethod]
def auth_request(client: Client, req: Request): applies = filter(client.auth_methods, lambda x: x.matches(req)) if len(applies) > 1: raise Exception("multiple auth methods found") elif len(applies) == 1: method = applies[0] method.apply_to_request(method.credentials, method.cache, req)
Then you would statically define your service's auth methods by URL, so users can just do:
my_client = Client(auth_methods=MyServicesAuthMethods)
or you can create a helper function like:def create_client_a(base_url: str): return Client(base_url, auth_methods=[...])
Alternately I think you can use closures, but I'm not totally positive about the scoping.
AuthMethod = typing.Callable[[Request], None]
def auth_method_a(username, password) -> AuthMethod: my_token = None
def __auth_method_a(req: Request):
if my_token is None:
my_token = do_login(username, password)
req.headers['token'] = my_token
return __auth_method
That creates a standard interface (a function that accepts a Request), without assuming anything about the underlying protocol.