Choć sam konta w Książeczce Maryja nie posiadam, przyszło mi ostatnio ścierać się z ichnim API, coby dla klienta wdrożenia poczynić. Po przejrzeniu dostępnych bibliotek (w tym dość żałosnego python-sdk), stanęło na dość popularnym projekcie pyfacebook.

Oryginalne repozytorium było wybrakowane pod względem funkcjonalności, wybraliśmy więc fork, który z pewnością stworzył fan Władcy Pierścieni. Jak się miało później okazać, "jeden, by wszystkie zgromadzić i w ciemności związać" dość wiernie oddaje uzyskany efekt.

Człowiek głupi, to przy pierwszym problemie zajrzał w bebechy ofiary. Po tym stanął jak wryty i całe Satchmo przeleciało mu przed oczami. Na początek tradycyjny problem — przejście się w glanach po separacji warstw:

from threading import local

_thread_locals = local()
def get_facebook_client():
    """
    Get the current Facebook object for the calling thread.

    """
    try:
        return _thread_locals.facebook
    except AttributeError:
        raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.')
class FacebookMiddleware(object):
    """
    Middleware that attaches a Facebook object to every incoming request.
    The Facebook object created can also be accessed from models for the
    current thread by using get_facebook_client().

    callback_path can be a string or a callable.  Using a callable lets us
    pass in something like lambda reverse('our_canvas_view') so we can follow
    the DRY principle.
    """
    # ...

    def process_request(self, request):
        # ...
        _thread_locals.facebook = request.facebook = Facebook(self.api_key,
                self.secret_key, app_name=self.app_name,
                callback_path=callback_path, internal=self.internal,
                proxy=self.proxy, app_id=self.app_id, oauth2=self.oauth2)

Przerażające konstrukcje zaczęły się jednak później:

# generate the Facebook proxies
def __generate_proxies():
    for namespace in METHODS:
        methods = {}

        for method, param_data in METHODS[namespace].iteritems():
            methods[method] = __generate_facebook_method(namespace, method, param_data)

        proxy = type('%sProxy' % namespace.title(), (Proxy,), methods)

        globals()[proxy.__name__] = proxy

__generate_proxies()
class Facebook(object):
    """
    Provides access to the Facebook API.

    ...
    """

    def __init__(self, api_key, secret_key, auth_token=None, app_name=None,
                 callback_path=None, internal=None, proxy=None,
                 facebook_url=None, facebook_secure_url=None,
                 generate_session_secret=0, app_id=None, oauth2=False):
        # ...
        for namespace in METHODS:
            self.__dict__[namespace] = eval('%sProxy(self, \'%s\')' % (namespace.title(), 'facebook.%s' % namespace))

Skończyło się na własnym forku i refaktoryzacji tych i wielu innych fragmentów kodu. Poprawioną wersję można znaleźć na GitHubie.

Na koniec stare powiedzenie ludowe:

Gdy bowiem zoczysz, iż jest coś narzeczy, a za cwanego masz się i uważasz __globals__ i eval() za sprawy rozwiązanie, mylisz się wielce, przeto idź przypudrować nos¹.

¹ Ciało, 2003