lepture / authlib

The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS,JWE,JWK,JWA,JWT included.
https://authlib.org/
BSD 3-Clause "New" or "Revised" License
4.59k stars 461 forks source link

Add resolvable types to methods that return OAuth clients. #527

Open RileyMathews opened 1 year ago

RileyMathews commented 1 year ago

Is your feature request related to a problem? Please describe.

When clients are registered. Pylance is unable to resolve the type of client class that is retrieved from the registry.

from authlib.integrations.django_client import OAuth

oauth = OAuth()

oauth.register(
    "auth0",
    client_id="fake-id",
    client_secret="fake-secret",
    client_kwargs={
        "scope": "openid profile email",
    },
    server_metadata_url=f"https://my-tenant.us.auth0.com/.well-known/openid-configuration",
)

oauth.auth0.authorize_access_token()

A proposed solution from https://github.com/lepture/authlib/issues/410 was to use the create_client method instead but this also does not seem to work. The following code...

from authlib.integrations.django_client import OAuth

oauth = OAuth()

oauth.register(
    "auth0",
    client_id="fake-id",
    client_secret="fake-secret",
    client_kwargs={
        "scope": "openid profile email",
    },
    server_metadata_url=f"https://my-tenant.us.auth0.com/.well-known/openid-configuration",
)

auth0_client = oauth.create_client("auth0")

auth0_client.authorize_access_token()

Still produces this error...

image

Describe the solution you'd like

I think the OAuth classes in each integration could define the register and create_client methods (the Flask client appears to already do this for some unique logic specific to Flask) while mostly just calling the super class methods but also define the return type specific to them in a type annotation. For example the Django integration could look something like...

class OAuth(BaseOAuth):
    oauth1_client_cls = DjangoOAuth1App
    oauth2_client_cls = DjangoOAuth2App
    framework_integration_cls = DjangoIntegration

    def create_client(self, name) -> DjangoOAuth2App:
        return super().create_client(name)

#...

This would allow the class inheritance structure to remain the same while giving the benefits of the IDE knowing what type is being returned. There is a slight issue with trying to figure out if you are dealing with an OAuth1 or OAuth2 version of the class. But the interfaces for those seem to remain consistent at the method level so maybe that wouldn't be as much of an issue.

Describe alternatives you've considered

I've tried overriding the return types in comments in my code but (at least for pylance) it still complains as it can't confirm the methods will always return a DjangoOAuth2App instance.

Additional context

I would be happy to try to open a PR for this if the solution proposed above seems tenable. Thanks!

spolloni commented 3 days ago

@lepture any thoughts here?