PyconPL 2009

No, byłem. Co do konferencji jako takiej to wolę się nie wypowiadać, ale wnioskuję, że nie byłem jej targetem — był może jeden wykład, na którym dowiedziałem się czegoś nowego i jeden, który obejrzałem dla przyjemności. Aspekt społeczny w pewnym stopniu został zaspokojony przez bratanie się z kolegami z Allegro.pl. Infrastrukturę (dostęp do sieci, wyżywienie, zakwaterowanie, napoje, przekąski) pominę milczeniem. W końcu też w niedzielę dałem spicz i odniosłem wrażenie, że pomimo pewnych problemów technicznych się podobał. Czyli: pod względem merytorycznym niejaki sukces.

Dla uzupełnienia:

  • prezentacja do obejrzenia/ściągnięcia jest na SlideShare
  • kod w takiej wersji, jaka była prezentowana, jest na BitBucket

Jeżeli czas pozwoli, to skrobnę też jakiś artykuł na podstawie tego, co zostało powiedziane.

Autentykacja, podejście drugie

Ponownie brudzę sobie ręce w aplikacji zrobionej przy użyciu alternative stack (Werkzeug + Jinja2 + SQLAlchemy + WTForms), trochę przy okazji przygotowań do mojego nadchodzącego wystąpienia na PyconPL 2009. I ponownie wszystko szło gładko, dopóki nie musiałem zabrać do autentykacji uwierzytelniana (nie autoryzacji).

Krąży po mieście plotka, że najlepiej do tego użyć AuthKit lub repoze.who. Jak w każdej plotce, w tej także może być ziarno prawdy, więc kilka dni temu postanowiłem to sprawdzić. Już początki nie były zachęcające (każda z tych bibliotek doinstalowuje w zależnościach połowę Pylons), a potem było jeszcze gorzej.

AuthKit okazał się tak mocno związany z Pylons, że nawet nie ma własnej dokumentacji, tylko jakieś rozdziały w Pylons Book. Jedyne, co nadawało się do użycia bez Pylons, to kilka przykładów w repozytorium. Spróbowałem pokonać niechęć i zapoznać się bliżej z tą biblioteką, ale dałem za wygraną po godzinie zastanawiania się, jak do tego bydlaka podejść. Może jakoś niedługo przyjdzie mi do głowy idea, jak mógłbym z tego skorzystać bez Pylons (a może ktoś to opisze...).

Dokumentacja do repoze.who okazała się dużo lepsza, ale tym razem przerosła mnie idea tej biblioteki. Przeczytałem wprowadzenie i zagotowało mi się pod kopułą — jak to w ogóle działa? I najważniejsze, jak to podłączyć do istniejącego kodu? Jestem pewien, że dokumentacja dokładnie opisuje każdy aspekt działania tej biblioteki, ale ja tego zwyczajnie nie zrozumiałem. Może dlatego, że przez cały czas przed oczami miałem mój use case, który obejmuje jedynie mały fragmencik tego, do czego ta biblioteka została stworzona.

Na dzień 10 lipca 2009 roku konkluzja jest taka, że albo przysiądę fałdów i spróbuję użyć jednej z tych dwóch bibliotek, albo pójdę na skróty i użyję RPX (czy czegoś w tym stylu). Co biorąc pod uwagę obecny trend w branży nie wydaje się być takim głupim rozwiązaniem. Niestety, pomijając całkowite niedopasowanie tego rozwiązania do docelowej grupy użytkowników mojej aplikacji...

Zapraszam na PyconPL 2009

Jeżeli ktoś jeszcze się zastanawia, czy wybrać się na PyconPL do Ustronia w październiku, to może przekonam go tym, że będę tam trzymał spicz na temat przygód Adama Słodowego w krainie ramówek webowych — rozłożę na czynniki pierwsze jakąś ramówkę (pewnie będzie to najbliższe mi Django) i spróbuję złożyć coś podobnego używając zamienników.

Dawno nie dawałem żadnego występu publicznego (od ostatniego WarPY w październiku minie 2 lata...), więc trzeba przygotować lepszy show. :)

Bez dumy

Zainspirowany komentarzem tylika przemyślałem sobie jeszcze raz kwestię mojej odysei egzaminacyjnej. Tylik, ja naprawdę nie mam się czym chwalić.

Mam 38 lat, od około 20 jeżdżę różnymi pojazdami, małymi i dużymi, szybkimi i wolnymi, na gąsienicach i na kołach, takimi co pływają i takimi, co się ledwo toczą, od Fiata 126p do BRDM-2, od Ursusa C-330 do BWP-1 (zgadza się, dłużej jeżdżę, niż piszę programy!). Wydawało mi się, że z takim doświadczeniem zdanie egzaminu państwowego będzie niemal formalnością, jednak przykładnie odjeździłem wszystkie jazdy, na luzaku przyjechałem w grudniu na egzamin i... przeżyłem pierwszy szok, gdy oblałem egzamin na łuku. Dlaczego, przecież niewiele to się różni od wyjeżdżania na parkingu czy z garażu (w przypadku naszego garażu jest nawet mniej miejsca)? Zagadka. Potem zagadek było jeszcze więcej — na egzaminie robiłem rzeczy, o które sam bym się nie podejrzewał, że w ogóle potrafię. Dopiero za piątym razem pozbierałem się jakoś i wreszcie ten egzamin zdałem. Do nikogo nie mogę mieć pretensji poza sobą.

Wszystko to może świadczyć o tym, że moje zdanie o sobie samym jest całkowicie błędne — psychicznie daleko mi do zimnego twardziela, który każdy stres bierze na miękko, robi co ma zrobić i idzie na wódkę po skończonej robocie. Dlatego właśnie czuję się psychicznie złomotany, pomimo tego, że oczywiście cieszę się ze zdanego egzaminu i zakończenia okresu przejściowego. Jednym zdaniem: powód do radości tak, ale do dumy to już nie — jest się z czego cieszyć, ale nie ma czym chwalić.

Co oczywiście nie psuje mi nastroju na tyle, żebym siedział w kącie i pochlipywał pod nosem — trzeba się zająć nową sytuacją na poważnie. Tylik ma swojego jednorożca, mam i ja ;). Celuję trochę niżej: Peugeot 406 Coupe 3.0 V6, ale przez to mam nadzieję ustrzelić sztukę nieco mniejszym wysiłkiem (głownie finansowym). Pewnie jeszcze trochę o tych polowaniach na mojego jednorożca tu się pojawi, a z obecnego stanu rynku (okazy można znaleźć np. na allegro) wnioskuję, że parę sztuk zląduje też na bezwypadkowe.net.

Igranie z przyszłością

Zmiany się szykują... Wspominałem o planach przepisania silnika tego bloga przy użyciu narzędziówki, ale im dłużej nad tym siedzę, tym bardziej jestem przekonany, że nie ma to wielkiego sensu — zbyt wiele rzeczy wymagałoby ręcznej ingerencji, patchowania komponentów i rwania włosów z głowy, bo coś nie działa, jak na przykład odkrycie z wczoraj:

  • nie działa Beaker z powodu błędu w kodzie backendów sesyjnych wykorzystujących bazę danych i memcached (pliki działają OK, ale ich nie chcę);
  • w związku z tym, że nie działa Beaker, nie działa też AuthKit, więc nici z ułatwień autentykacji (np. po OpenID).

Oczywiście, byłbym w stanie naprawić Beaker'a, powstaje jedynie pytanie, czy to się w ogóle opłaca? Alternatywą jest przejrzenie i odchudzenie istniejącej aplikacji w Django, a przy okazji zoptymalizowanie jej nieco, wykorzystując doświadczenie, jakiego nabrałem w ciągu tych kilkunastu miesięcy. To też mogłoby być nienajgorsze rozwiązanie, a na pewno szybsze. Będę żałował Jinja2, ale jak mnie żal przyciśnie, to podłączę sobie ten silnik do Django, żeby stonować trochę ten żal...

Poważnie się zastanawiam i jeszcze nie podjąłem żadnej decyzji — na razie kod wykorzystujący narzędziówkę poszedł do oddzielnego brancha w repozytorium, a w trunku pojawił się kod wersji działającej obecnie, ale nie ma to jakiegoś symbolicznego znaczenia (tak sobie to tłumaczę). Uch, jak ja lubię mieć takie dylematy... :)

Magia zrozumiana

Jestem po kilku tygodniach dłubania aplikacji webowej przy użyciu zestawu narzędzi (Werkzeug, SQLAlchemy lub datastore, Jinja2, WTForms) zamiast Django. Zawsze uważałem Django za kawał świetnego softu, ale dopiero teraz doceniam to, na ile ramówka aplikacyjna ułatwia życie programisty, załatwiając masę rzeczy z kategorii boilerplate przy użyciu kawałka swojej magii.

Narzędziówka jest pozbawiona zupełnie magii. To surowy zestaw narzędzi (niektóre z nich, jak SQLAlchemy mają własną magię wewnętrzną do załatwiania swoich spraw) i żeby zagrały ze sobą w zgodnej orkiestrze i razem umożliwiły napisanie dobrego kodu aplikacji, trzeba napisać sporo rzeczy: procesory żądań (w Django nazywa się to middleware), procesory kontekstu, podsystem konfiguracyjny, skróty ułatwiające życie (jak render_to_response() czy funkcja do odwracania URL-i), podsystem autoryzacji. W Django to wszystko już jest, gotowe i zainstalowane, a użycie jest tylko kwestią podłączenia w konfiguracji lub nawet zaimportowania odpowiedniego modułu — i za sprawą magicznej różdżki masz sitemapę, masz feed RSS/Atom, masz bufor stron i masę innych drobiazgów, na napisanie których poświęciłbyś trochę czasu, bo trzeba to mieć, pomimo tego, że te wszystkie rzeczy nie są corem aplikacji. Łatwo jest to wszystko docenić: działające zręby bloga w Django (tego bloga) miałem po około 2 godzinach roboty (wpisy, etykiety, archiwum, feed z wpisów i sitemapa), a powtórzenie tego samego wyczynu z użyciem zestawu narzędzi zajęło mi ponad 6 godzin pracy, z czego co najmniej 3 spędziłem na użeraniu się z duperelami. Warto było, bo moja wiedza na temat działania aplikacji w środowisku WSGI jest teraz o wiele większa. I lepiej rozumiem bebechy Django.

Z drugiej strony nie można powiedzieć, żeby ta wyprawa miała jedynie edukacyjną wartość — dorobiłem się sporej ilości kodu narzędziowego, który może mi się kiedyś przydać. Zobaczyłem też, jak wydajna i oszczędna w wykorzystaniu zasobów może być mała aplikacja, jeżeli się ją rozsądnie napisze, dla porównania aplikacja tego bloga żre na megiteam.pl ~20MB pamięci rezydentnej, a moja reimplementacja przy użyciu zestawu narzędzi jedynie ~12MB — różnica jest kolosalna (nie wspominam już o tym, że jest o wiele szybsza, prawdopodobnie dzięki Jinja2). Przyszłością tego bloga jest właśnie reimplementacja przy użyciu zestawu narzędzi, głównie w celach oszczędnościowych.

Napisawszy tyle wypadałoby przejść do jakiejś konkluzji... Jak dla mnie to każdy kto robi w webie przy użyciu pythonowych ramówek (nie tylko Django, to się tyczy również Pylons i TurboGears w takim samym stopniu), powinien zrobić sobie taką krótką wycieczkę do źródeł. Będzie mógł wtedy dostrzec zarówno mocne jak i słabe strony używanej przez siebie ramówki, a przede wszystkim zrozumie, jak jej wszystkie komponenty współgrają ze sobą, tworząc całość, która tak bardzo ułatwia pracę.

Zrób swoje własne Django, odc. 2 - procesory kontekstu

Obiecałem, że będzie o procesorach kontekstu jak w Django (a może nawet trochę lepszych), więc proszę.

Procesory kontekstu

Procesor kontekstu wg. Django to kod wykonywalny, który przyjmując obiekt request zwraca słownik, który następnie jest dodawany do kontekstu przed renderowaniem szablonu. W ten sposób można zapewnić sobie dostępność pewnych danych w kontekście bez potrzeby pamiętania o tym, by te dane tam umieścić. W Django procesory kontekstu są wywoływane wtedy, gdy używa się klasy RequestContext. Pół biedy, gdy chodzi o mój kod — już ja o to zadbam, żeby tam był użyty RequestContext, ale problemem może być kod, którego autor o tym nie pomyślał.

Używając Jinja2 i Werkzeug nie możemy liczyć na tak silną integrację systemu szablonów z modelem żądania i odpowiedzi (założenia obydwu komponentów właśnie taką integrację wykluczają). Na szczęście zarówno Jinja2 jak i Werkzeug dostarczają nam wszystkiego czego potrzeba, żeby zaimplementować sobie taką funkcjonalność. Niestety, nie będzie tak prosto jak w przypadku middleware.

Werkzeug posiada coś, co jest nazywane local proxy — jest to wariant thread local storage, specjalnie przystosowany do potrzeb aplikacji webowych. Zazwyczaj umieszcza się w nim kod aplikacji WSGI (klasy lub funkcji), ale nie ma przeszkód, żeby umieścić tam cokolwiek innego, np. obiekt request:

def __call__(self, environ, start_response):
    local.application = self
    local.request = request = Request(environ)

Tak umieszczony obiekt request będzie następnie dostępny w dowolnej innej części aplikacji w trakcie jej czasu życia (czyli przetwarzania żądania i produkowania odpowiedzi). Teraz w okolicy mojego kodu renderującego szablon mogę zrobić taki myk:

def build_globals():
    jinja_globals = {}
    for item in getattr(settings, 'CONTEXT_PROCESSORS', []):
        try:
            processor = import_string(item)
            jinja_globals.update(processor(local.request))
        except (ImportError, AttributeError):
            pass
    return jinja_globals

jinja_env = Environment(loader=FileSystemLoader(settings.TEMPLATE_DIRS))
jinja_env.globals.update(build_globals())

Chodzi o to, że Jinja2 posiada tzw. globalne zasoby — można je zmodyfikować po utworzeniu środowiska a przed wyrenderowaniem pierwszego szablonu. Od teraz w każdym szablonie będzie dostępne to, co znajduje się w moim słowniku jinja_env.globals. A co się tam może znaleźć? Na przykład to:

def messages(request):
    messages = request.environ['beaker.session'].get('messages', [])
    request.environ['beaker.session']['messages'] = []
    request.environ['beaker.session'].save()
    return {
        'MESSAGES': messages,
    }

Czemu jest to lepsze rozwiązanie niż w Django? Ponieważ nie trzeba pamiętać o tym, żeby kontekst był obiektem klasy RequestContext. Dzięki buforowaniu środowiska przez Jinja2, narzut jest minimalny, a prostota rozwiązania kusząca. Czyżby więc było to rozwiązanie idealne? W moim przypadku nie. Trzeba bardzo uważać na to, by zaimportować ten kod w odpowiednim momencie (dla uproszczenia umieściłem go w przestrzeni globalnej). Ale działa i robi dokładnie to, co chciałem.

Ten kod nie powstałby, gdyby nie Ali Afshar (tak, ten od PIDA), który podał rozwiązanie tego problemu na StackOverflow.

Zrób swoje własne Django, odc. 1 - middleware

Żartuję, Django jest na tyle dobre, żeby nie musieć go robić samemu. Chodzi mi raczej o sytuację, kiedy chce się mieć tyle Django, ile trzeba i ani trochę więcej (na przykład dlatego, że z całego oryginalnego Django wykorzystuje się tylko parę komponentów, bo na więcej nie pozwala AppEngine). Z pomocą przychodzi Werkzeug, Beaker i Jinja2 i parę innych bibliotek. Oprócz oczywistego zysku, jakim jest odchudzenie narzędziówki o kilka cennych setek plików, można zyskać bezcenną wiedzę, jak działa Django (im więcej wiem, tym większym podziwem darzę core devs, bo Django okazuje się jeszcze lepsze, niż wygląda na pierwszy rzut oka).

W paru kolejnych odcinkach opiszę rzeczy, które uznawałem za bardzo użyteczne w Django i zaimplementowałem sobie jako uzupełnienie narzędziówki (inna rzecz, że niełatwo jest się wyrzec niektórych przyzwyczajeń).

Middleware

Nie chodzi mi o middleware w rozumieniu WSGI, lecz takie, jak w Django — metody zwykłych klas, które są wywoływane z ustalonymi argumentami w określonych momentach przetwarzania żądania i odpowiedzi. Niespodzianka, Werkzeug ma już coś takiego! Nazywa się to Processor i klasa, która znajduje się w module werkzeug.contrib.kickstart pokazuje, jak powinien wyglądać kompletny interfejs takiej klasy i sygnatury jej metod. Leniwi mogą sobie z tej klasy po prostu odziedziczyć...

Pozostaje jedna rzecz, czyli podłączenie tego cuda do naszej aplikacji WSGI. Poniżej fragment mojej metody __call__() z klasy reprezentującej aplikację WSGI. Podłączam w niej tylko jeden rodzaj middleware (są jeszcze 3 inne możliwe).

for item in getattr(settings, 'MIDDLEWARES', []):
    try:
        klass = import_string(item)
        middleware = klass()
        response = middleware.process_request(request)
    except (ImportError, AttributeError):
        pass
    if response is not None:
        break
if response is None:
    local.url_adapter = adapter = url_map.bind_to_environ(environ)
    try:
        endpoint, values = adapter.match()
        handler = import_string(endpoint)
        response = handler(request, **values)
    except HTTPException, e:
        response = e

Inny sposób na podłączenie do kodu aplikacji WSGI można znaleźć na wiki Werkzeug — jak zwykle pełna wolność wyboru metody (oczywiście, działa dobrze w każdym przypadku).

W następnym odcinku

A w następnym odcinku okaże się, jak zrobić procesory kontekstu (context processors) takie jak w Django, a nawet lepsze. Tym razem w użyciu będzie zarówno Werkzeug jak i Jinja.

Ładowanie...