Fatih Erikli python - django develöper

Python İstanbul Mayıs buluşmasından notlar

Bu ay da çok hareketli ve güzel geçen bir Python İstanbul buluşmasını 20 Python severin katılımıyla gerçekleştirdik. Geçen ay olduğu gibi bu buluşmada da Hipo ofisinde toplandık. Cumartesi günümüz python severlerle burada bir kahvaltı ile başladı, muhabbet ve sunumlarla devam etti. Gerçekten cumartesi günümü çok iyi değerlendirdim diyebilirim. 

Bu toplantıda 3 sunum yapıldı, sunumlardan birisini de benimdi. Sunumlar sırası ile şöyle yapıldı;

  • Pyİst geçmişi ve yön haritası (Metin Amiroff)
  • Celery ve RabbitMQ (ben)
  • PyPy & Just-in-time (JIT) compilation (Berker Peksağ)

Celery Sunumu
Sunumda bir AMQP protokolü implementation’ı olan RabbitMQ‘yu ve bunu message broker olarak kullanabilen Celery‘nin bize getirdiği kolaylıklardan bahsettik. Sunum için ayrıca bir de demo application hazırlama fırsatı olmuştu. Bununla birlikte bir de Onur Çelebi Connect 4 oyunu için hazırladığı algoritmanın Celery halini hazırlayıp bizlere sundu. 

Celery sunumuna şuradan ulaşabilirsiniz;
http://fatiherikli.com/pyist/pyist-celery.pdf

Demo uygulamayı incelemek isterseniz de;
https://github.com/fatiherikli/downforeveryoneorjustme

Onur Çelebi’nin hazırladığı demo application’a da ulaşır ulaşmaz buraya linkini koyacağım.

PyPy sunumu
Berker Peksağ hazırladığı sunumda ise PyPy‘ın sağladıkları, PyPy alternatifleri ve development sürecini anlattı. Ayrıca PyPy’ın getirdiği avantajlardan ziyade dezavantajlarına da değindi. Hoş bir sunum olmuş gerçekten. PyPy hakında değişik bilgiler edindim. Kendisine buradan teşekkürlerimi sunuyorum :)
Sunuma aşağıdaki linkten ulaşabilirsiniz;
http://berkerpeksag.github.com/slides/pypy-101-pyist/pypy-101-pyist.pdf


Python’cuların kedi sevdası
Buluşma sadece sunumlardan ibaret değildi. Hatta sunumdan çok vakti hoş sohbet, geyik ve Nerd muhabbetiyle geçirdik :)
Bunun dışında toplantıda bütün Python’cuların gönlünü kazanan minik bir misafirimiz daha vardı. Daha doğrusu pek misafir denmez, ev sahibi demek daha doğru olur. Ona herkes hipo kedi diyor. Gerçi pek rahat durmadı ama olsun, toplantıya neşe kattı diyebiliriz. Ayrıca hipo kedi sayesinde community’deki kedi severleri de tespit etmiş olduk. 

Yine bir ay sonraki toplantıyı dört gözle bekliyorum. Haziranda bir Hack Day yapılması planlanıyor. Python ile uğraşan ya da merak salan herkese kaçırmamalarını tavsiye ederim :)

Backbone.js, Django ve Tastypie üçlüsü ile bir scrum board uygulaması

Backbone javascript ile mvc (model-view-controller) iskeleti üzerinde genişleyebilir (scalable) uygulama geliştirmenizi sağlayan bir kütüphanedir. Underscore.js ve jQuery ile birlikte kullanılmaktadır.

Uzun zamandır backbone ile scrum board tarzı bir şey yapasım vardı. En sonunda yaptım, ve kodlarını github üzerinde açtım. Aşağıdaki linkten inceleyebilirsiniz;

http://scrumboard.herokuapp.com/

Heroku üzerinde free olarak yayınladığım için biraz yavaş olabilir. O yüzden siz en iyisi aşağıdaki gibi bilgisayarınıza kurupta çalıştırın :)

$ git clone git://github.com/fatiherikli/scrumboard.git
$ cd scrumboard
$ pip install -r requirements.txt 
$ python scrumboard/manage.py runserver 

scrumboard/static/js/app dizini altındaki controllers.js, views.js ve models.js dosyalarını inceleyebilirsiniz.

Ayrıca kodlara aşağıdaki linkten ulaşabilirsiniz. Hatta hoşunuza gider ve forklarsanız geliştirmeye birlikte devam edebiliriz :)

https://github.com/fatiherikli/scrumboard

Django’da admin üzerinde çalışırken hayatınızı kolaylaştıracak iki decorator

def allow_tags(func):
"""
Bir ModelAdmin uzerinde yazdiginiz fonksiyonun html olarak cikti verebilmesini saglar.
"""
func.allow_tags = True return func def short_description(description):
"""
ModelAdmin uzerinde yazdiginiz fonksiyonlara short_description atamasi yapar.
""" def wrap(func):
func.short_description = description return func return wrap

Şu şekilde kullanabilirsiniz;

class FooBarAdmin(admin.ModelAdmin):
list_display = ('__unicode__', 'my_method')

@allow_tags
@short_description('Benim bold kolonum')
def my_method(self, obj):
return '<strong>selamm !</strong>' 

12 Şubat - Python İstanbul buluşması

Pyist’e ilk defa bugün katıldım. Şansıma bugün diğer buluşmalara nazaran beklenenden daha kalabalık bir toplantı olmuş. Yaklaşık 20 kişiydik. Gerçekten çok zevkli ve heyecan verici bir buluşmaydı.

Her ne kadar projeksiyon cihazı bulamasakta konuşmacı arkadaş (Taylan Pince) sunum konusunu çok başarılı bir şekilde anlattı. Sunumun konusu django ile RESTful API’ler geliştirmemizi kolaylaştıran django-tastypie idi. Bu zamana kadar REST API için piston kullanmaktaydım. Zannedersem bugünkü buluşmadan sonra tastypie kullanacağım :)

Böyle bir organizasyonu düzenlediği için başta Cihan Okyay‘a ve diğer emeği geçen herkese teşekkürlerimi sunuyorum. 

Sunum dosyaları github’taki pyist organizasyonu altında yayınlanacaktır.

MongoDB database yapısı üzerinde MapReduce işlemleri

Bildiğiniz üzere MongoDB document-oriented bir NoSQL veritabanıdır. Verileri JSON şeklinde saklayıp, JSON şeklinde erişiyoruz. Daha doğrusu BSON (Binary JSON) şeklinde. 

NoSQL database sistemleri üzerinde group, distinct gibi işlemler ilişkisel veritabanlarına (mysql, postgresql) nazaran biraz daha karmaşık. Bunun gibi sorgular fonksiyonel programlama’nın temellerinden olan MapReduce işlemi ile yapılmaktadır. MapReduce MongoDB üzerinde built-in olarak gelmektedir. 

Map ve Reduce fonksiyonları

Sorgular için iki adet fonksiyon yazıyorsunuz; map ve reduce. Şimdilik MongoDB üzerinde sadece javascript ile yazılabiliyor.

  • Map: parametre olarak her bir dökümanı (ilişkisel veritabanlarındaki satır) alır ve key, value şeklinde dönüş yapar.
  • Reduce: map fonksiyonundan gelen key, value değerlerini parametre olarak alır ve verilerinizi bu fonksiyonda işlersiniz.

Bir örnek yapalım

Myadslot.com üzerinden örnek vermek istiyorum; istatistik dataları benim üşengeçliğimden dolayı MySQL database üzerinde diğer kayıtlar ile birlikte saklanmaktaydı. Bu gece vaktimi eski verileri MongoDB’ye aktarmak için data-migration yazarak geçirdim. MongoDB üzerinde saklanan datalar şu şekilde;

{
    "last_visit_date": "2012-01-26 18:05:41",
    "slot_id": 9,
    "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) ",
    "last_visit_url": "http://dbdsgnr.appspot.com/",
    "visit_count": 2,
    "ip_address": "72.179.56.244",
    "advertisement_id": 7
}

Problem şu;

Slot istatistikleri sayfasında slota ait tüm reklamların görüntülenme sayısının toplamının alınması gerekiyor. Aslında bu data zaten slot üzerinde denormalize edilmiş durumda, maksat örnek olsun :)

Örnekte MongoDB client’i olarak pymongo kullanacağız. Veritabanına bağlanmak için şöyle bir fonksiyon kullanabiliriz;

import pymongo
def get_db(db_name="myadslot"):
    return pymongo.Connection()[db_name]

Map ve reduce fonksiyonlarımızı yazalım. 

map_slots = """
function () {
    emit(this.slot_id, this.visit_count);
}
"""

Gördüğünüz üzere map edilen dökümana (row) this keyword’ü ile ulaşabilmekteyiz. slot_id’sini key, visit_count’u ise value olarak belirledik. Şimdi reduce fonksiyonumuzu yazalım.

reduce_slots = """
function (key, values) {
    var total = 0;
    for (var i in values) {
        total += values[i]
    }
    return total
}
"""

Reduce fonksiyonumuza slot_id ve visit_count parametre olarak geliyor. Biz burada slota ait tüm reklamların visit_count’unun toplamını döndürüyoruz. Python içinde javascript birazcık pis oluyor gördüğünüz üzere. Ancak MongoDB’nin sonraki versiyonlarında ek olarak başka script dilleri kullanabileceğiz. Şimdi bu fonksiyonlarımızı MongoDB üzerinde çalıştıralım.

db = get_db()
result = db["visitors"].map_reduce(map_slots, reduce_slots, "slot_stats")
for item in result.find():
    print item

Map reduce işleminden sonra bize geçici bir koleksiyon oluşturulmakta. Geçici koleksiyonumuzun adını slot_stats olarak belirledik. Bu koleksiyon üzerinden tekrar filtreleme yapabilmekteyiz ancak biz hepsini yazdırdık. İşlemin sonucu şu şekilde olacaktır;

{u'_id': 4L, u'value': 17.0}
{u'_id': 5L, u'value': 335909.0}
{u'_id': 6L, u'value': 3L}
{u'_id': 9L, u'value': 3736.0}
{u'_id': 10L, u'value': 4.0}
... 

Ayrıca django ile birlikte MongoDB kullanımı için aşağıdaki bağlantıları göz atmanızı tavsiye ederim.

Birazcık scaling: Python ve Django uygulamalarında asenkron işler (RabbitMQ)

Konuya girmeden önce; artık blog post’larım django.org.tr gezegeninde yayınlanmakta. Bu yüzden bana tumblr’ın getirtiği baştan salma yazı yazma alışkanlığımı sonlandırmak istiyorum. Başlıksız ya da içeriksiz post atmak her ne kadar güzel olsa da post’lar sadece tumblr üzerinden okunmuyor. Artık özen göstererek yazmak gerekecek :)

Distributed Computing

RabbitMQ,  zaten dağıtık hesaplama (distributed computing) konusunda ünlü olan Erlang dili ile geliştirilmiş AMQP(Advanced Message Queuing Protocol) protokolü üzerinde çalışan open-source bir message broker (mesaj kuyruğu diyebiliriz) yazılımıdır. Bu ve bunun gibi message broker yazılımları sayesinde kullandığımız dilden bağımsız bir şekilde yapılması uzun süren işlemleri (hesaplamalar, email vb.) çeşitli makinalara ya da aynı makina üzerindeki birden fazla worker’a dağıtabiliriz.

Gerçek hayattan bir örnek verecek olursak; scale edilmesi gereken bir django uygulamasında request ve response arasında uzun süren işlemler ya da web ile alakası olmayan ayrı bir katmanda yapılan bir iş olmaması gerekmektedir. Misal kullanıcılara bir ya da birden fazla email göndermek, uzun süren hesaplamalar yapmak, kullanıcıya yeni bir demo site açmak gibi …

Requirements;

  • Erlang platformu (Linux üzerindeyken RabbitMQ kendisi yüklemekte. Eğer windows üzerinde iseniz şuradan indirip kurabilirsiniz.)
  • RabbitMQ Server (Windows’ta iseniz şuradan indirebilirsiniz)
  • Kullanacağımız programlama dili için RabbitMQ client kütüphanesi. Örneklerde python için pika kullanacağız. Diğer diller için kütüphaneler şurada.

Paket manager’ımız ile rabbitmq-server’ı yükleyelim.

apt-get install rabbitmq-server

Kurulum bittiğinde server otomatik olarak başlatılacaktır. Şimdi de python kütüphanemizi yükleyelim.

pip install pika

Bu işlem de bittikten sonra yükleyeceğimiz başka bir şey kalmıyor. Örneklere geçebiliriz.

Bir mesaj kuyruğu uygulamasında üç temel yapı vardır; Consumer, Queue ve Publisher.

Consumer

Publisher’dan gelecek olan mesajlar için sürekli dinlemede olan bir nevi sunucudur. Aynı anda birden fazla consumer çalıştırabilirsiniz. RabbitMQ tüm consumer’lara eşit miktarda iş yükü dağıtmaya çalışacaktır. 

Queue

Task yani görevlerin saklandığı kuyruktur. Eğer tek consumer var ise bu yapı FIFO (First in First out) yani -ilk giren ilk çıkar- şeklindedir. Zira birden fazla consumer olduğunda RabbitMQ işleri dağıtmaktadır.

Publisher

Consumer’a işleri gönderen uygulamadır. Bu bir django application’ı olabilir.

Şimdi örneğimize geçelim; peş peşe email gönderen bir uygulama simüle edelim. İlk olarak receiver yani consumer uygulamamızı yazalım. 

consumer.py içeriği;

import pika, time

def callback(ch, method, properties, body):
    print "email gonderiliyor; ", body
    time.sleep(1) # gercekci olsun diye 1 saniye bekletiyoruz :)
    print "email gonderildi."

def main():
    connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='mailing')
    channel.basic_consume(callback, queue='mailing', no_ack=True)
    channel.start_consuming()

if __name__ == "__main__":
    main()


Örnekte mailing adında bir kuyruk oluşturduk ve o kuyruk üzerinde consuming işlemini başlattık. Örneği çalıştırdığınızda program dinlemeye geçecektir. Bir mesaj geldiğinde ise oluşturduğumuz callback fonksiyonu mesaj parametresi ile birlikte işlemeye başlayacaktır. Consumer uygulamamızı aşağıdaki gibi başlatalım;

python consumer.py

Şimdi ise publisher’ımızı yazalım.

publisher.py içeriği;

import sys
import pika

def main():
    connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='mailing')
    channel.basic_publish(exchange='', routing_key='mailing',body=sys.argv[1])
    connection.close()

if __name__ == "__main__":
    main()

Consumer uygulamamızdaki gibi aynı kuyruğa bağlandık ve bu sefer kuyruğa mesaj göndermesini istedik. Publisher uygulamamızı aşağıdaki gibi çalıştırdığımızda dinlemede olan consumer’ımız üzerine görevi yapacaktır.

python publisher.py fatiherikli@gmail.com

Burada önemli olan kısım publisher’dan ziyade consumer uygulamamızın yapılandırmasıdır. Örnekte biz tek bir consumer uygulaması çalıştırdık. Peş peşe publisher.py üzerinden komut göndermeye çalıştığınızda Consumer’ınız sırasıyla gönderdiğiniz komutları çalıştıracaktır.

Şimdi ise consumer.py ‘nizi farklı terminallerde aynı anda çalıştırın. Örnek olarak 3 tane consumer’ınız dinlemede olsun. Publisher’ınız üzerinden peş peşe mesaj göndermeye çalıştığınızda her consumer’da aynı görev sayısı olacak şekilde işlemlerin dağıtıldığını göreceksiniz.

Ayrıca Django ve RabbitMQ etkileşimini daha da kolaylaştıran celery projesini incelemenizi tavsiye ederim;

http://celeryproject.org/

kank.im’in kodları artık açık

şuradan göz atabilirsiniz;
https://github.com/fatiherikli/kankim 

 denemek için;

  • git clone git://github.com/fatiherikli/kankim.git
  • cd kankim
  • python manage.py syncdb
  • python manage.py runserver

kankim’i ise şurada.


backends: django’da string ile belirtiğiniz class’ı import etmek

özellikle settings.py’de backend ya da middleware tanımlarında görmüşsünüzdür. bu teknik design patterns listelerinde Backend, Backend Interface şeklinde geçmekte. bugün böyle bir şey lazım oldu. django’nun kodunu inceledim ve ufak bir helper fonksiyon yazıverdim.

örnek olarak setting.py’nizde şöyle bir tanımınız var;

SPIDER_BACKEND = "fgame.game.backends.GameSpiderBackEnd"

bunu class’ı şu fonksiyon ile kullanabilirsiniz.

def import_class(class_path):
    from django.utils.importlib import import_module
    module_name = '.'.join(class_path.split(".")[:-1])
    mod = import_module(module_name)
    return getattr(mod, class_path.split(".")[-1])

şöyle de bir örnek yapalım;

from django.conf import settings
klass = import_class(settings.SPIDER_BACKEND)
instance = klass() 

django’da sitemap framework ve ping_google işlemi

django’nun tadından yenmeyen en güzel yanlarından bir tanesi daha; sitemap framework. sitemap ile ilgili işlemlerinizi aşırı derecede kolaylaştırmakta ayrıca tek bir manage.py komutu ile sitemap’ınızı google’a bildirmenizi sağlamaktadır.

hemen bir senaryo düşünelim. varsayalım Game adında bir modelimiz olsun. her yeni oyun eklediğimizde oluşan sitemap’ı google’a biz hiç bir şey yapmadan bildirsin. aynı zamanda kodlarımız tertemiz olsun.

ilk olarak manage.py üzerinden ping_google komutunu kullanabilmemiz için sitemaps paketini INSTALLED_APPS’e eklememiz gerekmekte.

INSTALLED_APPS = (
    ...
    'django.contrib.sitemaps',
    ...
)

şimdi de game isimli application’ımızda sitemaps.py şeklinde bir dosya açalım ve sitemap’imizi oluşturalım.


from django.contrib.sitemaps import Sitemap
from game.models import Game

class GameSiteMap(Sitemap):
    changefreq = "never"
    priority = 0.5

    def items(self):
        return Game.objects.all()

    def lastmod(self, obj):
        return obj.create_date

sitemap’ımızı oluşturmuş olduk. evet bu kadar :) hatta class oluşturmadan direk urls.py içinde kullanabildiğiniz GenericSitemap adında bir class’ı da bulunmakta. ancak bu şekilde daha okunabilir ve düzenli olmakta kanımca.

şimdi /sitemap.xml şekilde erişim için urls.py dosyamızı şu şekilde ayarlayalım.

from game.sitemaps import GameSiteMap
sitemaps = {
    'game' : GameSiteMap
}

urlpatterns = patterns('',
    ...
# sitemaps (r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}) )

tarayıcınızda /sitemap.xml’i açtığınızda sitemap’in oluşmuş olduğunu göreceksiniz.
peki bu sitemap’i google’a en kolay yoldan nasıl bildiririz. şöyle;

python manage.py ping_google

bu işlemi yapabilmeniz için web sitenizin google webmaster tools’ta kayıtlı olması gerekmektedir.

her oyun eklendiğinde otomatik olarak sitemap’imizi bildirme işlemini yapmak için ise sinyallerle ya da direk modelimizin save metodunu ezerek yapabiliriz.

from django.contrib.sitemaps import ping_google
class Game(models.Model): # ... def save(self, **kwargs): super(Game, self).save(**kwargs) try: ping_google() except: pass


bu kadar. salın kağlıcakla :)