Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Hi, I've been exploring this package and am very excited about the output it generates. Especially the extensibility due to the jinja2 templating is great! Thank you for maintaining it. 🙇

After generating a client I noticed that there's a lot of duplication between AuthenticatedClient and Client. From what I can tell the usage of AuthenticatedClient is:

  1. To pre-load Client with authentication headers to authenticate requests.
  2. To have good type hinting in endpoint_module's, showing when an endpoint required authentication.

Setting authentication headers

Currently we solve for this usecase:

client = Client() # will make unauthenticated requests
auth_client = AuthenticatedClient(token='my-token') # will make authenticated requests

However, when the token needs to change (f.e. b/c different users use different tokens) then we'd have to completely change the client. Leading to the httpx.ClientSession being recreated and causing overhead.

auth_client1 = AuthenticatedClient(token='user1-token') # will make authenticated requests for user1
auth_client2 = AuthenticatedClient(token='user2-token') # will make authenticated requests for user2

It might be nicer to instead have methods on Client to set and del authorization headers so that the Client can be longlived and the authentication can vary per request.

client = Client()
client.with_authorization_token(token='user1-token')
client.request() # will make auth requests for user1
client.with_authorization_token(token='user2-token')

The added benefit here is that we can solve for #823 by adding with_authorization_basic as well. Without duplicating Client more.

Type hinting

To make clear when an endpoint requires authentication headers set we can add a phantom type parameter.

from base64 import b64encode
from typing import Dict, Generic, TypeVar, cast

from attrs import define, evolve, field


class Unauthenticated: ...


class Authenticated: ...


_State = TypeVar("_State")


@define
class _Client(Generic[_State]):
    _headers: Dict[str, str] = field(factory=dict, kw_only=True)

    def with_headers(self, headers: Dict[str, str]) -> "Client":
        return evolve(self, _headers={**self._headers, **headers})

    def with_authorization_token(
        self: "Client",
        token: str,
        prefix: str = "Bearer",
    ) -> "AuthClient":
        return cast("AuthClient", self.with_headers({"Authorization": f"{prefix} {token}"}))

    def with_authorization_basic(
        self: "Client",
        username: str,
        password: str,
        prefix: str = "Basic",
    ) -> "AuthClient":
        encoded_credentials = b64encode(f"{username}:{password}".encode()).decode()
        return cast("AuthClient", self.with_headers({"Authorization": f"{prefix} {encoded_credentials}"}))

    def remove_headers(self: "AuthClient") -> "Client":
        return evolve(self, _headers={})


Client = _Client[Unauthenticated]
AuthClient = _Client[Authenticated]


def my_func(client: AuthClient) -> None:
    # only accepts authenticated clients
    print("got headers:", client._headers)


# --- usage demos ---
c0 = Client()  # Unauthenticated
my_func(c0)  # static type error

c1 = c0.with_authorization_token("my-token")  # Authenticated
my_func(c1)  # ok

c2 = c1.remove_headers()  # back to Unauthenticated
my_func(c2)  # static type error

c1 = c0.with_authorization_basic("my-username", "my-password")  # Authenticated
my_func(c1)  # ok

Let me know what you think. I'm happy to suggest the PR 🙇 and thanks again!

You must be logged in to vote

Replies: 2 comments

Comment options

Oh, and this would be a breaking change as AuthenticatedClient would not take token as input anymore.

You must be logged in to vote
0 replies
Comment options

cc @johnthagen @dbanty

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
1 participant
Morty Proxy This is a proxified and sanitized view of the page, visit original site.