See no eval, hear no eval, speak no eval

3. wrz 2010

Choć sam konta w Ksią­żeczce Maryja nie posia­dam, przy­szło mi ostat­nio ście­rać się z ich­nim API, coby dla klienta wdro­że­nia poczy­nić. Po przej­rze­niu dostęp­nych biblio­tek (w tym dość żało­snego python-sdk), sta­nęło na dość popu­lar­nym pro­jek­cie pyfa­ce­book.

Ory­gi­nalne repo­zy­to­rium było wybra­ko­wane pod wzglę­dem funk­cjo­nal­no­ści, wybra­li­śmy więc fork, który z pew­no­ścią stwo­rzył fan Władcy Pier­ścieni. Jak się miało póź­niej oka­zać, „jeden, by wszyst­kie zgro­ma­dzić i w ciem­no­ści zwią­zać” dość wier­nie oddaje uzy­skany efekt.

Czło­wiek głupi, to przy pierw­szym pro­ble­mie zaj­rzał w bebe­chy ofiary. Po tym sta­nął jak wryty i całe Satchmo prze­le­ciało mu przed oczami. Na począ­tek tra­dy­cyjny pro­blem — przej­ście się w gla­nach po sepa­ra­cji 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)

Prze­ra­ża­jące kon­struk­cje zaczęły się jed­nak 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ła­snym forku i refak­to­ry­za­cji tych i wielu innych frag­men­tów kodu. Popra­wioną wer­sję można zna­leźć na GitHu­bie.

Na koniec stare powie­dze­nie ludowe:

Gdy bowiem zoczysz, iż jest coś narze­czy, a za cwa­nego masz się i uwa­żasz __globals__ i eval() za sprawy roz­wią­za­nie, mylisz się wielce, przeto idź przy­pu­dro­wać nos¹.

¹ Ciało, 2003

There are 10 comments in this article:

  1. 4. wrz 2010Głupi says:

    A mógł­byś tro­che wyja­śnić tak żeby ci głupsi mogli sie też cze­gos nauczyc?

  2. 4. wrz 2010Patrys says:

    Wyja­śniam:

    Ewa­lu­acja jest zła i powinno się jej uni­kać jak ognia.

  3. 4. wrz 2010crackcomm says:

    Rozu­miem, że eval jest zły dla­tego, że łatwo jest wstrzyk­nąć ‘zło­śliwy’ kod ?

  4. 4. wrz 2010Patrys says:

    eval jest zły, bo nigdy nie jest wła­ści­wym roz­wią­za­niem, uczy poten­cjal­nie nie­bez­piecz­nych prak­tyk (stąd rodzą się potworki w rodzaju JSONP) i nie pozwala na pod­sta­wie kodu okre­ślić, jak zacho­wuje się program.

  5. 22. paź 2010owca says:

    Ostat­nio udalo mi sie unik­nac imple­men­ta­cji fejs­bu­ko­wego api, ale poszu­ki­wa­nia cze­gos dzia­la­ja­cego tez zajely mi wcze­sniej dluz­sza chwile. A spraw­dza­les te django-facebookconnect, django-facebook-oauth i django-socialauth ?

  6. 22. paź 2010Patrys says:

    owca:

    Nie spraw­dza­łem, bo potrze­bo­wa­łem korzy­stać z peł­nego API, nie samego logowania.

  7. 21. lis 2010trunk says:

    niby wszystko faj­nie, tylko znik­nela polowa kodu m.in „get_facebook_client” i bez tuto­riala ciezko teraz tego uzywac

  8. 22. lis 2010Patrys says:

    trunk:

    Klienta dosta­jesz z obiek­tem request. Nie potrzeba do tego żadnych spe­cjal­nych funk­cji. Jeśli nie masz requ­esta, użyj kon­struk­tora klasy Facebook. Uży­wa­nie thread locals to zło.

  9. 22. lis 2010trunk says:

    cool :) A czy mogl­bys jesz­cze sie podzie­lic jak uzy­wac tego jako samo­dziel­nej a nie fejs­bu­ko­wej apli­ka­cji ? facebook.login_required auto­ma­tycz­nie prze­nosi na strone fb a po zalo­go­wa­niu prze­rzuca do cal­l­back url. Czy nalezy spraw­dzic sobie ‘zalo­go­wa­nie’ w sesji i kie­ro­wac do tem­plejta z logo­wa­niem czy jesz­cze jakos inaczej ?

  10. 22. lis 2010Patrys says:

    Deko­ra­tory dedy­ko­wane są dla apli­ka­cji webo­wych, ale nic nie stoi na prze­szko­dzie, żeby spraw­dzać request.facebook.uid i odpo­wied­nio ofe­ro­wać wła­sne logo­wa­nie. Można też użyć require_oauth (zakła­dam, że uży­wasz aktu­al­nego kodu z https://github.com/mirumee/pyfacebook).

Write a comment:

Creative Commons Attribution-NonCommercial-ShareAlike 2.5 Poland
This work by Patryk Zawadzki is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 Poland.
cheap online pharmacy