Wie implementiere ich OAuth2 in meine requests.Session Klasse?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
12vich2o
User
Beiträge: 2
Registriert: Montag 4. März 2024, 20:57

Hi,

ich stehe vor einem Problem das ich anscheinend nicht selbst gelöst bekomme. Ich habe bereits eine ganze Weile recherchiert, aber auch das hat mich nicht voran gebracht. Ich hoffe inständig dass ihr mir hier vielleicht weiterhelfen könnt, bzw. mich in die richtige Richtung weisen könnt.

Ich habe eine BaseClient Klasse die von requests.Session, requests_cache.session.CacheMixin und requests_ratelimiter.LimiterMixin erbt. Die Klasse dient als Grundgerüst für Implementationen diverser API clients. Zwei Clients die einen API key nutzen habe ich bereits testweise aufgebaut und das funktioniert wunderbar.

Code: Alles auswählen

from requests import Session
from requests_cache import CacheMixin
from requests_ratelimiter import LimiterMixin
from pyrate_limiter import SQLiteBucket
from urllib.parse import urljoin


class BaseClient(CacheMixin, LimiterMixin, Session):
    def __init__(self, *, cache_name="cache", cache_lifetime=-1, **kwargs):
        super().__init__(
            # requests_cache
            cache_name=cache_name,
            expire_after=cache_lifetime,
            allowable_methods=['GET', 'POST'],
            # requests_ratelimiter
            bucket_class=SQLiteBucket,
            max_delay=10,  # in seconds
            **kwargs
        )

    def request(self, method, endpoint, *args, **kwargs):
        return super().request(method, urljoin(self.base_url, endpoint), *args, **kwargs)
Nun möchte ich einen weiteren API client implementieren, der nun allerdings OAuth2 nutzen muss. Dafür möchte ich requests-oauthlib mit dem Backend Application Flow verwenden, da der API Anbieter (DigiKey) mir CLIENT_ID und CLIENT_SECRET zur Authentifizierung zur Verfügung stellt, siehe hier. Das Beispiel aus der Dokumentation funktioniert eigenständig auch wunderbar:

Code: Alles auswählen

import os
from dotenv import load_dotenv
from rich import print
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

load_dotenv()

client_id = os.getenv("DIGIKEY_API_CLIENT_ID")
client_secret = os.getenv("DIGIKEY_API_CLIENT_SECRET")

client = BackendApplicationClient(client_id=client_id)
oauth = OAuth2Session(client=client)

token = oauth.fetch_token(
    token_url='https://sandbox-api.digikey.com/v1/oauth2/token',
    client_id=client_id,
    client_secret=client_secret,
    include_client_id=True
)

print(token)
Nun möchte ich diese Funktionalität in meine DigiKey Klasse implementieren. Diese erbt vom BaseClient und zusätzlich von requests_oauthlib.OAuth2Session. (Später werden weitere OAuth2 clients hinzu kommen, von daher wird das dann irgendwann wohl in eine BaseClientOAuth o.Ä. Klasse wandern...) Ich habe das folgendermaßen versucht:

Code: Alles auswählen

from pyrate_limiter import Duration, RequestRate, Limiter
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
from urllib.parse import urljoin
from .utils import BaseClient


class DigiKey(BaseClient, OAuth2Session):
    limit_per_minute = RequestRate(120, Duration.MINUTE)
    limit_per_day = RequestRate(1000, Duration.DAY)
    limiter = Limiter(limit_per_minute, limit_per_day)

    base_url = "https://sandbox-api.digikey.com"

    def __init__(self, *, client_id, client_secret, **kwargs):
        self.xclient_id = client_id
        self.xclient_secret = client_secret
        self.xclient = BackendApplicationClient(client_id=self.xclient_id)
        super().__init__(cache_name="temp/digikey", limiter=self.limiter, client=self.xclient, **kwargs)

        self.token = self.fetch_token(
            token_url=urljoin(self.base_url, "/v1/oauth2/token"),
            client_id=self.xclient_id,
            client_secret=self.xclient_secret,
            include_client_id=True
        )
        print(self.token)
Das resultiert jedoch in folgendem Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/12vich2o/projekte/api-client/oauth-test.py", line 27, in <module>
    digikey = DigiKey(client_id=client_id, client_secret=client_secret)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/12vich2o/projekte/api-client/api_client/digikey.py", line 21, in __init__
    self.token = self.fetch_token(
                 ^^^^^^^^^^^^^^^^^
  File "/home/12vich2o/projekte/api-client/.venv/lib/python3.11/site-packages/requests_oauthlib/oauth2_session.py", line 251, in fetch_token
    raise ValueError(
ValueError: Please supply either code or authorization_response parameters.
Warum fehlt hier anscheinend ein Parameter?
Egal wie sehr ich mich auch bemühe und in den Klassen Definitionen etc. rum suche, ich komme einfach nicht drauf was hier nicht passt. :cry:

Ich bin für jede Hilfe dankbar! :roll:
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ohne das tiefer nachvollzogen zu haben ist da ggf super das Problem (pooof, gleich kommt ein wildes __blackjack__ in den Chat….😬). Das wirkt so, als ob deine Argumente falsch verteilt und/oder verschluckt werden.

Dinge, die ich probieren würde:

- Reihenfolge ändern, MRO sollte links -> rechts sein, also dein OAuthSession2 mal nach vorne.
- Explizit __init__ aufrufen, statt super.
- step-debugging durch den Konstruktionsprozess.
12vich2o
User
Beiträge: 2
Registriert: Montag 4. März 2024, 20:57

__deets__ hat geschrieben: Dienstag 5. März 2024, 08:44 - Reihenfolge ändern, MRO sollte links -> rechts sein, also dein OAuthSession2 mal nach vorne.
wow... das hat funktioniert. Vielen Dank!

Allerdings weiß ich noch nicht genau warum... da werde ich mich nach der Arbeit nochmal dahinter klemmen, denn verstehen will ich das eigentlich schon auch. :ugeek:
Was meinst du mit "MRO"?
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Method resolution order, die legt fest, in welcher Reihenfolge bei komplexen Vererbungshierarchien Methoden aufgerufen werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten