Nerdstuff am Morgen: Python Stuff

Servus Menschen, ich lebe noch! \o/

Viel ist passiert in den letzten ... VIER MONATEN?!? (Wow, wie die Zeit vergeht...)

Zum Beispiel sind wir umgezogen. Oh, und ausgewandert. ^^

Aaaaaaaber darum gehts heute nicht. Über die letzten Monate zu schreiben würde viel zu lange dauern und wäre viel zu emotional und blah. Kein Bock.

Darum gibts stattdessen einfach mal einen kleinen Nerdpost:

Twittering around with Python

(Disclaimer: Der folgende Code wurde in den frühen Morgenstunden nach definitiv zu wenig Schlaf geschrieben. Für syntaktische Richtigkeit würde ich also echt nicht garantieren. Aber die Ideen sollten verständlich sein.)

In letzter Zeit hab ich viel mit der Twitter API gespielt. Dazu verwende ich die IMO sehr angenehme Python Library “Python Twitter Tools”.

(Nicht zu verwechseln mit einem der diversen anderen, ähnlich benannten twitter libs für Python. XD )

Wenn ich jetzt größere Dinge mit der Twitter API mache (zB. versuche den sozialen Graphen von GG zu erstellen) komm ich halt schnell mal an das API Limit, woraufhin die API einen 429 HTTP Error schmeißt.

Noch nicht so das Drama, für sowas gibt es ja Exceptions die man fangen kann.

Doch nicht nur das, PTT bringt auch schon selber mit, daß Twitter Connectors (Also das Objekt, das im Code die Schnittstelle zur API ist.) kann auch selber warten, indem man ihm ein “retry=True” als Argument bei der Initialisierung mitliefert:

from twitter import *

t = Twitter(
    auth=OAuth(token, token_key, con_secret, con_secret_key),
    retry=True)

Super praktisch.

Trotzdem kann es sein, daß gewisse Dinge mit dem API Limit trotzdem sehr, sehr lange brauchen, darf man doch teilweise für manche Endpunkte nur 15 Anfragen pro 15 Minuten Slot machen.

Eine Möglichkeit ein bißchen mehr Anfragen zu bekommen ist einen zweiten Connector mittels “Application-only Authentication” zu verbinden. Damit kann sich das Programm separat zum benutzten User gegenüber der API authentifizieren und bekommt ein eigenes (meist kleineres) Kontingent für Anfragen.

In der Praxis sieht das so aus:

from twitter import *

bearer_token = oath2_dance(CONSUMER_KEY, CONSUMER_SECRET)

app = Twitter(
    auth=OAauth2(bearer_token=bearer_token))

(Die app-only Variante geht nur mit OAuth2. Details dazu siehe PTT “Doku”.)

Alles schön und gut, aber jetzt brauch im, um für die jeweiligen Anfragen zwischen den beiden zu wechseln noch einen Block mit Exceptionhandling Foo:

try:
    # Stelle Anfrage mit twitter connector
    t.some.request(...)
except TwitterHTTPError as e:
    # Check ob das API Limit erreicht ist 
    if e.e.code == 429:
        # Probiers nochmal mit dem app-only connector
        try:
            app.some.request(...)
        except TwitterHTTPError as e:
            # falls auch hier das Limit erreicht wird:
            if e.e.code == 429:
                # warte
                sleep(some_seconds)

Alles natürlich nur grober Pseudocode, aber schon für den Anfrang recht praktisch. Einzig: Das ist schon ein ziemlich fetter, hässlicher Blob Code. Wär doch toll wenn das schöner geht.

Decorator to the rescue!

Ich hab in der Vergangenheit viel gestrauchelt mit Pythons Decorators, vor allem weil ich für mich selber den Anwendungsfall noch nicht wirklich hatte. (Das andere Leute/Projekte diese extensiv verwenden ist mir klar, hat aber nicht wirklich geholfen.)

Im Zuge des obrigen Rumbastelns glaube ich jetzt eine Lösung gefunden zu haben, die mir sehr zusagt:

# Wir bauen uns einen Decorator
def load_balance(func1, func2):
    def newfunc(*args, **kwargs):
        try:
            return func1(*args, *kargs)
        except TwitterHTTPError as e:
            if e.e.code == 429:
                try:
                    return func2(*args, **kwargs)
                except TwitterHTTPError as e:
                    if e.e.code == 429:
                        sleep(sometime)
                        newfunc(*args, **kwargs)
                    else:
                        raise e
            else:
                raise e
    return newfunc

# Jetzt benutzen wir ihn.
make_requests = load_balance(t.some.request, app.some.request)

Dieser Decorator nimmt also nun zwei Funktionen als Argument und gibt eine neue Funktion zurück. Wird diese ausgeführt, wird zuerst versucht die eine Funktion auszuführen. Scheitert dies Aufgrund des API Limits (Wir bekommen also eine TwitterHTTPError Exception mit Fehlercode 429.), dann wird versucht die zweite Funktion mit den selben Argumenten auszuführen. Geht auch das wegen des API Limits nicht, so warten wir eine Zeit und probieren es dann nochmal.