BOTTLE ile Web
GİRİŞ
Merhaba. Ben Python'u henüz bilmeyen bir PHP yazılımcısıyım. Bu yayını, bottle adlı framework'ün ilgimi çekmesi üzerine konuyla ilgili Türkçe içerik oluşturmak üzere (aslında kendime not düşmek ve sizinle de notlarımı paylaşmak amacıyla) oluşturuyorum. Python üzerinde bi'şeyler öğrendikçe de bu blog'da paylaşmayı düşünüyorum.
Bottle'ı bize bizzat kendi sitesi öğretirse pek güzel olur diye düşündüm. Bu yayında orijinal bir içerik üretmek yerine siteyi Türkçeye çevirerek paylaşacağım. Siz de lütfen yanlış gördüğünüz veya yayına dahil etmemi istediğiniz konular olursa yorumlarla bildirin ki hem beni araştırmaya teşvik etmiş olun hem de birlikte güzel bir Türkçe kaynak oluşturmuş olalım.
Paylaşılan örnekleri internet üzerinde test etmezsek olmaz. Yani her şeyin benim localhost'umda olması benim için pek bi'şey ifade etmiyor. Ama sunucu kiralamak için de kaynak ayırmak istemiyorum. Bu yüzden ben sunucu olarak kendi bilgisayarımı kullanıyorum. Bottle zaten bilgisayarımda bir localhost oluşturuyor. Modem ayarlarımdan da port yönlendirmeyle (8080 portunu kullanıyorum) ip adresimi internete açıyorum. Bu yüzden bu paylaşımları Python yüklü gerçek bir sunucuda nasıl paylaşabilirsiniz, şimdilik bilmem. Bu da burada not olsun ki ileride böyle bi'şey yaparsam buraya link koyarım. :)
İçeriği paylaşırken bazı bilinmesi gereken yerleri öylesine çeviri arasına kaynatmak yerine üst simge olarak (1 gibi) belirtip sayfanın altında açıklamaları yazacağım veya araya kendi yorumlarımı sıkıştırmak istediğimde alt simge (böyle) olarak yazacağım. Aklımızın takılacağı yerler muhakkak olacaktır ama böyle olursa bunu minimize etmiş oluruz.
Bottle: Python Web Framework *
Bottle, Python için hızlı, basit ve hafif bir WSGI1 mikro web framework'üdür. Tek bir dosya modülü olarak dağıtılır ve Python standart kütüphanesi2 dışında hiçbir bağımlılığı yoktur.
- Routing: Sabit veya dinamik url isteklerini (localhost:8080/hello/world) fonksiyonlarınıza kolayca yönlendirebilme.
- Templates: Hızlı ve pythonik yerleşik template motoru (html view'larınızla Python kodlarınızın etkileşimi için) ve mako, jinja2 and cheetah template'leri için destek. (Bunları henüz bilmiyorum ama göz gezdirdiğim kadarıyla bottle'da view'lar içine veri yazma olayı php'deki kadar (hatta vue.js kadar) kolay.
- Utilities: Form verilerine, dosya yüklemelerine, cookie'lere, header'lara ve HTTP ile ilgili diğer meta verilere kolay erişim.
- Server: Yerleşik HTTP geliştirme sunucusuna ek olarak paste, fapws3, bjoern, gae, cherrypy veya diğer WSGI özellikli HTTP sunucuları için de destek. (Biz şimdilik destekleri boş verip yerleşik özellikleri kullanalım...)
Örnek: bottle'da "Hello World"
from bottle import route, run, template
@route('/hello/<name>')
def index(name):
return template('<b>Hello {{name}}</b>!', name=name)
run(host='localhost', port=8080)
Bu script'i çalıştırın veya bir Python konsoluna yapıştırın. Sonra da tarayıcınızdan http://localhost:8080/hello/world yazın. İşte bu kadar! (Tabi siz bunu hemen çalıştıramazsınız çünkü henüz Bottle'ı yüklemediniz. Yükleme işi bir sonraki başlıkta anlatılıyor: pip install bottle)
run(host='localhost', port=8080) ile yerel sunucumuzu başlatıp 8080 portunu dinlettik.4
Sunucumuz /hello/world şeklinde gelen request'i aldığı zaman bunu @route() yönlendiricisi (buna Python'da dekoratör deniyor sanırım) yardımıyla aldı ve index() adlı fonksiyona bağladı.
Fonksiyon da template() fonksiyonunu kullanarak ekrana çıktıyı bastı. Bu arada template içinde html kodlarının olduğunu ve bunların içine Python değişkenini yazmak için Vue.js'teki, Angular.js'teki gibi {{ }} kullandığımızı da gözden kaçırmayalım. Yukarıda bahsi geçen template'e bir ön bakış oldu.
İndirme ve Kurulum
pip install bottle ile son kararlı sürümü kurun veya bottle.py'yi (kararsız) indirip proje klasörünüze taşıyın.
Python standart kütüphanesi2 dışında bottle'ın başka kütüphanelere bağımlılığı yoktur. Bottle, Python 2.7 ve Python 3 sürümlerini destekler. Bottle'ın 0.13 sürümünden itibaren Python 2.5 ve 2.6 desteği kaldırıldı.
EĞİTİM *
Bu eğitim, size Bottle web framework'ündeki kavramları ve özellikleri tanıtıyor ve hem temel hem de ileri düzey konuları kapsıyor. Eğitimi baştan sona ders gibi çalışabilir veya Bottle'ı kullanırken ihtiyaç duydukça incelemek için kullanabilirsiniz. Otomatik olarak oluşturulan API Referansı size garip gelmiş olabilir. (Yani hiç sınıf oluşturmadan nasıl Python'un standart kütüphanesinde2 olmayan run(), route(), template() gibi fonksiyonları print()'i kullanır gibi kullanabildik?)3 Bu konu biraz detay içeriyor ama eğitimin ilerleyen safhalarında buna değinilecek. En yaygın sorular için çözümler Tarifler koleksiyonumuzda veya Sıkça Sorulan Sorular sayfasında bulunabilir. Yaygın sorular için çözümler Tarifler (örnekler) sayfasında veya Sıkça Sorulan Sorular sayfasında bulunabilir. Yardıma ihtiyacınız olursa, e-posta listemize katılın veya IRC kanalımızda bizi ziyaret edin.
Kurulum
Bottle herhangi bir harici kitaplığa bağlı değildir. Sadece bottle.py'yi proje dizininize indirebilir ve kodlamaya başlayabilirsiniz:
$ wget https://bottlepy.org/bottle.py
Bu size tüm yeni özellikleri içeren en son geliştirme anlık görüntüsünü verecektir. Daha kararlı bir ortam tercih ediyorsanız, kararlı sürümlere bağlı kalmalısınız. Bunlar PyPI'de mevcuttur ve pip (önerilen), easy_install veya paket yöneticiniz aracılığıyla yüklenebilir:
$ sudo pip install bottle # önerilen
$ sudo easy_install bottle # pip'e alternatif
$ sudo apt-get install python-bottle # debian, ubuntu vb. kullanım
Her iki durumda da, bottle uygulamalarını çalıştırmak için Python 2.7 veya daha yenisine (3.4+ dahil) ihtiyacınız olacak. Sistem genelinde paket kurma izniniz yoksa veya sadece kurmak istemiyorsanız, önce bir sanal ortam (virtualenv) oluşturun:
$ virtualenv develop # Sanal ortam oluşturma
$ source develop/bin/activate # Varsayılan Python'u sanal olanla değiştirme
(develop)$ pip install -U bottle # Bottle'ı sanal ortama kurma
Veya sisteminizde virtualenv kurulu değilse:
$ wget https://raw.github.com/pypa/virtualenv/master/virtualenv.py
$ python virtualenv.py develop # Sanal ortam oluşturma
$ source develop/bin/activate # Varsayılan Python'u sanal olanla değiştirme
(develop)$ pip install -U bottle # Bottle'ı sanal ortama kurma
HIZLI BAŞLANGIÇ: HELLO WORLD
Artık bottle'ın kurulu olduğunu veya projenizin dizininde bulunduğunu varsayarak devam ediyoruz. Çok basit bir "hello world" örneğiyle başlayalım:
from bottle import route, run
@route('/hello')
def hello():
return "Hello World!"
run(host='localhost', port=8080, debug=True)
Budur! Bu .py dosyasını çalıştırın. http://localhost: 8080/hello adresini ziyaret edin ve "Merhaba Dünya!" tarayıcınızda! Şu şekilde çalışıyor:
route() dekoratörü, bir kod parçasını bir URL yoluna bağlar. Bu durumda, /hello yolunu hello() fonksiyonuna bağladık. Buna route (yönlendirme) (diğer adıyla dekoratör) denir ve bu framework'ün en önemli kavramıdır. İstediğiniz kadar dekoratör tanımlayabilirsiniz. Bir tarayıcı bir URL talep ettiğinde, ilişkili fonksiyon çağrılır ve return edilen dönüş değeri tarayıcıya geri gönderilir. Bu kadar basit.
Son satırdaki run(), yerleşik bir geliştirme sunucusunu başlatır. localhost:8080 bağlantı noktasında çalışır ve terminalden ctrl+c'ye basana kadar isteklere hizmet eder. Sunucu back-end'ini daha sonra değiştirebilirsiniz, ancak şimdilik tek ihtiyacımız olan bir geliştirme sunucusu. Hiçbir kurulum gerektirmez ve uygulamanızı yerel testler için çalışır duruma getirmenin inanılmaz derecede zahmetsiz bir yoludur.
Hata Ayıklama Modu ön geliştirme sırasında çok faydalıdır, ancak genel uygulamalar için kapatılmalıdır. Bunu aklınızda tutun.
Buraya kadarki kısımda uygulamaların Bottle ile nasıl oluşturulduğuna dair temel konsepti gördük. Okumaya devam edin; başka nelerin mümkün olduğunu göreceksiniz.
VARSAYILAN UYGULAMA
Kolaylık olması açısından, bu eğiticide yer alan çoğu örnek, yolları tanımlamak için modül düzeyinde reoute() dekoratörü kullanır. Bu, route()'u ilk kez çağırdığınızda otomatik olarak oluşturulan global bir Bottle nesnesi oluşturup "varsayılan uygulamaya" yönlendirmeleri ekler3. Ama daha nesne yönelimli bir yaklaşımı tercih ediyorsanız (ki ben böyle yapmayı tercih ederim) ve bunun için fazladan kod yazmayı önemsemiyorsanız, ayrı bir uygulama nesnesi oluşturabilir ve bunu genel olanın yerine kullanabilirsiniz:
from bottle import Bottle, run
app = Bottle()
@app.route('/hello')
def hello():
return "Hello World!"
run(app, host='localhost', port=8080)
Nesneye yönelik yaklaşım, Varsayılan Uygulama bölümünde daha ayrıntılı açıklanmaktadırDemek ki biz de yazımızın ilerleyen bölümlerinde buna değineceğiz. Bunun da bir seçenek olduğunu bilin.
İSTEK YÖNLENDİRMEK
En son, yalnızca tek bir yönlendirmeyle çok basit bir web uygulaması oluşturmuştuk. "hello world" örneğinin yönlendirme kısmı şöyleydi:
@route('/hello')
def hello():
return "Hello World!"
route() dekoratörü, parametresinde belirtilen URL yolunu hemen altındaki callback fonksiyonuna bağlar ve varsayılan uygulamaya yeni bir yol ekler. Yine de tek yönlendirmeli bir uygulama biraz sıkıcıdır. Biraz daha ekleyelim (uygulamanın başına from bottle import template eklemeyi unutmayın):
@route('/')
@route('/hello/<name>')
def greet(name='Stranger'):
return template('Hello {{name}}, how are you?', name=name)
Bu örnek iki şeyi göstermektedir: Tek bir callback'e birden fazla yol bağlayabilir ve URL'lere joker karakterler ekleyebilir ve bunlara anahtar kelime argümanları aracılığıyla erişebilirsiniz.
DİNAMİK YÖNLENDİRMELER
Joker karakterler içeren rotalara dinamik yönlendirmeler rotalar diyelim artık denir statik rotaların aksine ve aynı anda birden fazla URL ile eşleşir. Basit bir joker karakter, açılı ayraçlar (ör. <ad>) içine alınmış bir addan oluşur ve bir sonraki eğik çizgiye (/) kadar bir veya daha fazla karakteri kabul eder. Örneğin, /hello/<ad> yolu /hello/alice ve /hello/bob için istekleri kabul eder, ancak /hello, /hello/ veya /hello/mr/smith için kabul etmez.
Her bir joker karakter, URL'in <kapsanand> kısmını, istek yapılan callback'ine aynı isimli değişken olarak iletir. Bunları hızlıca kullanabilir ve bir RESTful API için hoş görünümlü ve anlamlı URL'leri kolaylıkla uygulayabilirsiniz. Aşağıda, eşleşecekleri URL'lerle birlikte başka örnekler verilmiştir:
@route('/wiki/<pagename>') # örn: /wiki/Learning_Python ile eşleşir
def show_wiki_page(pagename):
...
@route('/<action>/<user>') # örn: /follow/defnull ile eşleşir
def user_api(action, user):
...
Filtreler, daha spesifik joker karakterler tanımlamak ve / veya URL'nin kapsanan kısmını callback'e iletilmeden önce dönüştürmek için kullanılabilir. Filtrelenmiş bir joker karakter, <ad:filtre> veya <ad:filtre:yapılandırma> olarak bildirilir. İsteğe bağlı yapılandırma bölümünün sözdizimi, kullanılan filtreye bağlıdır.
Aşağıdaki filtreler varsayılan olarak uygulanır ve istenirse daha fazlası da eklenebilir:
- :int yalnızca (pozitif) sayılarla eşleşir ve gelen değeri tam sayıya dönüştürür.
- :float :int gibidir. :int'in ondalıklı sayılara uyarlanmış hali.
- :path eğik çizgi (/) karakteri dahil tüm karakterleri eşleştirir ve birden fazla yol parçasını eşleştirmek için kullanılabilir.
- :re yapılandırma alanında özel bir normal ifade belirlemenize olanak tanır. Eşleşen değer değiştirilmez.
Şimdi bazı pratik örneklerle ne anlattığımıza bir göz atalım:
@route('/object/<id:int>')
def callback(id):
assert isinstance(id, int)
@route('/show/<name:re:[a-z]+>')
def callback(name):
assert name.isalpha()
@route('/static/<path:path>')
def callback(path):
return static_file(path, ...)
Kendi filtrelerinizi de ekleyebilirsiniz. Ayrıntılar için İstek Yönlendirmeleri sayfasını inceleyin. Umuyorum ki sırası gelince biz de bu yayında inceleyeceğiz
HTTP REQUEST FONKSİYONLARI
HTTP protokolü, farklı görevler için çeşitli istek yöntemlerini tanımlar. GET, başka bir yöntem belirtilmemiş tüm yollar için varsayılandır. Şimdiye kadar yazdığımız rotalar sadece GET metoduyla gelen isteklerle eşleşecektir. POST, PUT, DELETE veya PATCH gibi diğer yöntemleri işlemek için, route() dekoratörüne bir parametre ekleyin veya beş alternatif dekoratörden birini kullanın: get(), post(), put(), delete() veya patch().
POST metodu, genellikle HTML form gönderimi için kullanılır. Bu örnekle, POST kullanılarak bir giriş formunun nasıl işleneceğini gösterelim:
from bottle import get, post, request # or route
@get('/login') # ya da @route('/login')
def login():
return '''
<form action="/login" method="post">
Username: <input name="username" type="text" />
Password: <input name="password" type="password" />
<input value="Login" type="submit" />
</form>
'''
@post('/login') # ya da @route('/login', method='POST')
def do_login():
username = request.forms.get('username')
password = request.forms.get('password')
if check_login(username, password):
return "<p>Your login information was correct.</p>"
else:
return "<p>Login failed.</p>"
Bu örnekte /login URL'ine GET metoduyla gelen istekleri login(), POST metoduyla gelenleri ise do_login() fonksiyonları karşılıyor. İlk fonksiyon ile kullanıcı, giriş formunu HTML olarak görüntülüyor. İkinci fonksiyon da POST edilen form verilerini check_login() fonksiyonuyla (böyle bir fonksiyon yazmışız varsayın) kontrol edip sonucun ne olduğunu ekrana yazıyor. request.forms'un kullanımı, Request Data bölümünde ayrıntılı olarak açıklanmıştır.
ÖZEL METODLAR: HEAD ve ANY
HEAD yöntemi, bir GET isteğinden gelen meta bilgilerini almak için kullanılır. İçeriği almaz, sadece gelen isteğin header'ını alır ve bunu GET'e gönderir. Siz HEAD kullanmasanız da bu meta verilere sahip olursunuz ama size sadece bu meta'lar lazım olacaksa HEAD kullanabilirsiniz.
Ek olarak, standart olmayan ANY yöntemi düşük öncelikli bir callback olarak çalışır: ANY; "POST mu, GET mi, ne?" demeden HTTP yöntemlerine bakılmaksızın istekleri eşleştirir. Tabi yalnızca daha özel bir yol tanımlanmamışsa! Bu, istekleri daha belirli alt uygulamalara yeniden yönlendiren proxy yönlendirmeleri için yararlıdır.
Özetle; HEAD, isteklerin meta bilgilerini alıp GET'e yollar. ANY ise, istek eğer başka bir şeyle eşleşmiyorsa tüm istekleri kabul eder. Kısaca böyle bi'şey... Keşke örnekle pekiştirseymiş. Neyse bu kadarı da aklımızda bulunsun. Lazım olursa yerini bilelim...
STATİK DOSYALARI YÖNLENDİRMEK
Görüntüler (image'lar) veya CSS dosyaları gibi statik dosyalar otomatik olarak sunulmaz. Hangi dosyaların sunulacağını ve bunları nerede bulacağınızı kontrol etmek için bir yol ve callback eklemeniz gerekir:
from bottle import static_file
@route('/static/<filename>')
def server_static(filename):
return static_file(filename, root='/statik/dosyalarin/yolu')
static_file() fonksiyonu, dosyaları güvenli ve uygun bir şekilde sunmaya yardımcı olur. (Statik Dosyalar'a bakın). Bu örnek, /statik/dosyalarin/yolu dizininin içindeki dosyalarla sınırlıdır çünkü <filename> joker karakteri, içinde eğik çizgi bulunan bir yolla eşleşmeyecektir. Bu dizinin içindeki tüm alt dizinleri erişime açmak için şöyle bir yol izleyebilirsiniz:
@route('/static/<filepath:path>')
def server_static(filepath):
return static_file(filepath, root='/statik/dosyalarin/yolu')
root='./statik/dosyalar' şeklinde kullanırken dikkatli olun. Çalışma dizini (./) ve proje dizini her zaman aynı olmayabilir.
HATA SAYFALARI
Eğer bi'şeyler ters giderse Bottle bilgilendirici bir sayfa görüntüler ama siz varsayılan HTTP status code'larınızı (200 OK, 404 Page Not Found gibi) error() dekoratörü ile override edebilirsiniz:
from bottle import error
@error(404)
def error404(error):
return 'Üzgünüm. Sayfa bulunamadı.'
Böylece, 404 Dosya Bulunamadı hataları, kullanıcıya özel bir hata sayfası ("Üzgünüm. Sayfa bulunamadı." yazan bir sayfa) gösterecektir. Hata işleyiciye (error-handler) iletilen, sadece tek parametreli bir HTTPError nesnesi örneğidir. Bunun dışında, hata işleyici normal bir request fonksiyonuna oldukça benzer. HTTPError örneği dışında; request'i okuyabilir, response yazabilir ve desteklenen herhangi bir veri tipini döndürebilirsiniz.
Hata işleyicileri yalnızca uygulamanız bir HTTPError exception'ı (istisnası) döndürürse kullanılır. (abort() tam da bunu yapar. Bu konu ileride işleniyor) Yani Request.status'u değiştirmek veya HTTPResponse döndürmek hata işleyiciyi tetiklemez.
İÇERİK OLUŞTURMAK
Saf WSGI1 ile uygulamanızdan döndürebileceğiniz çok sınırlı sayıda tür vardır. Uygulamalar yinelenebilir (iterable) verimli bayt string'leri döndürmelidir. Siz bir string döndürebilirsiniz (çünkü stringler yinelenebilirdir) ama çoğu sunucuda içeriğiniz karakter karakter iletilir. Ama unicode karakterlere (i, ğ gibi karakterler 1 byte'ten çok yer kapladıkları için) hiçbir şekilde izin verilmez. Bu da haliyle pek pratik olmaz.
Bottle çok daha esnektir ve çok çeşitli türleri destekler. Hatta mümkünse ve unicode'u otomatik olarak kodlayıp header'a bir Content-Length ekler ve bunu sizin yapmanıza gerek kalmaz. Aşağıda, uygulama fonksiyonlarınızdan döndürebileceğiniz veri türlerinin bir listesi ve bunların framework tarafından nasıl ele alındığının kısa bilgileri yer almaktadır:
- Dictionary'ler:
- Yukarıda belirtildiği gibi, Python dictionary'ler (veya bunların alt sınıfları) otomatik olarak JSON string'e dönüştürülür ve tarayıcıya
Content-Typeheader'ıapplication/jsonolarak ayarlanmış şekilde gönderilir. Bu, JSON tabanlı API uygulamalarında kolaylık sağlar. JSON dışındaki veri formatları da desteklenmektedir. Daha fazla bilgi için tutorial-output-filter'a bakın. ...demiş ama tutorial-output-filter diye bi'şey yok. Link verilmemiş. - Boş String'ler, False, None veya Diğer non-true değerler:
- Bunlar,
Content-Lengthheader'ı 0 olarak ayarlanmış boş bir çıktı üretir. - Unicode String'ler
- Unicode string'leri (veya unicode string'leri üreten yinelemeler),
Content-Typeheader'ında (varsayılan olarak utf8) belirtilen codec ile otomatik olarak kodlanır ve ardından normal bayt dizeleri olarak değerlendirilir (aşağıya bakın). - Byte String'ler
- Bottle, string'leri bir bütün olarak döndürür (her karakter üzerinde yinelemek yerine) ve dize uzunluğuna bağlı olarak bir
Content-Lengthüstbilgisi ekler. Önce bayt string'lerin listeleri birleştirilir. Bayt string'leri veren diğer yinelenen değerler, belleğe sığmayacak kadar büyüyebileceğinden birleştirilmez. Bu durumdaContent-Lengthheader'ı ayarlanmamıştır. - HTTPError ve HTTPResponse nesne örnekleri
- Bunları return etmek, onları istisna olarak fırlatmakla aynı etkiye sahiptir. HTTPError durumunda, hata işleyici uygulanır. Ayrıntılar için yukarıdaki Hata Sayfaları başlığına bakın.
- File object'ler
DEVAM EDECEK...
1: WSGI: Web Server Gateway Interface (Web Sunucusu Ağ Geçidi Arayüzü). Bir web sunucusunun web uygulamalarıyla nasıl iletişim kurduğunu ve bir isteği işlemek için web uygulamalarının nasıl zincirlenebileceğini açıklayan bir özelliktir. WSGI, PEP 3333'te ayrıntılı olarak açıklanan bir Python standardıdır.
2: Python Standard Library. Python'un hiçbir import'a gerek duymadan doğrudan çalıştırabildiğimiz fonksiyonlarını içeren kütüphane. print() gibi... Yani bottle'ı kullanmak için sadece bottle'a ihtiyacımız olacak. :)
3: Bottle'ın eğitimi olaya gizem katmış ama biz aklımıza takılmaması için konuya açıklık getirelim. forum.yazbel.com/t/python-bottle-framework/847 adresinde bu konuya şu cümleyle açıklama getirilmiş: "Siz @route dekoratörünü bir kere kullandığınızda, otomatik olarak global bir Bottle() sınıfı oluşturulur ve buraya eklenir."
4: Siz de benim gibi port yönlendirmeyle dışarıdan sitenize erişmek isterseniz bu yöntem işe yaramayabilir. Eğer bu komutun portunuza gelen her isteği karşılamasını isterseniz "localhost" yerine "0.0.0.0" yazabilirsiniz. Böylece ip'nize port üzerinden gelen her istek (127.0.0.1 veya localhost veya direkt cihazınıza atanan ip) yakalanmış olur.
Yorumlar
Yorum Gönder